diff --git a/scripts/importTrackMateTracks.m b/scripts/importTrackMateTracks.m index fdbf643cf..782495fa9 100644 --- a/scripts/importTrackMateTracks.m +++ b/scripts/importTrackMateTracks.m @@ -81,24 +81,29 @@ %% Load and Test compliance try - doc = xmlread(file); - catch %#ok - error('Failed to read XML file %s.',file); + doc = matlab.io.xml.dom.Parser().parseFile(file); + catch ME + switch ME.identifier + case 'MATLAB:UndefinedFunction' + error("Your MATLAB is too old (pre-R2021a) to run this script.") + otherwise + error(ME.identifier,'Failed to read XML file %s.',file); + end end root = doc.getDocumentElement; - if ~strcmp(root.getTagName, 'Tracks') + if ~strcmp(root.TagName, 'Tracks') error('MATLAB:importTrackMateTracks:BadXMLFile', ... 'File does not seem to be a proper track file.') end %% Get metadata - metadata.spaceUnits = char( root.getAttribute('spaceUnits') ); - metadata.timeUnits = char( root.getAttribute('timeUnits') ); + metadata.spaceUnits = root.getAttribute('spaceUnits'); + metadata.timeUnits = root.getAttribute('timeUnits'); metadata.frameInterval = str2double( root.getAttribute('frameInterval') ); - metadata.date = char( root.getAttribute('generationDateTime') ); - metadata.source = char( root.getAttribute('from') ); + metadata.date = root.getAttribute('generationDateTime'); + metadata.source = root.getAttribute('from'); %% Parse @@ -109,17 +114,17 @@ for i = 1 : nTracks - trackNode = trackNodes.item(i-1); + trackNode = trackNodes.node(i); detectionNodes = trackNode.getElementsByTagName('detection'); nSpots = str2double( trackNode.getAttribute('nSpots') ); - nSpots = min( nSpots, detectionNodes.getLength() ); + nSpots = min( nSpots, detectionNodes.Length ); - A = NaN( nSpots, 4); % T, X, Y, Z + A = zeros( nSpots, 4); % T, X, Y, Z for j = 1 : nSpots - detectionNode = detectionNodes.item(j-1); + detectionNode = detectionNodes.node(j); t = str2double(detectionNode.getAttribute('t')); x = str2double(detectionNode.getAttribute('x')); y = str2double(detectionNode.getAttribute('y')); diff --git a/scripts/trackmateEdges.m b/scripts/trackmateEdges.m index 22044adf7..e03fa8f17 100644 --- a/scripts/trackmateEdges.m +++ b/scripts/trackmateEdges.m @@ -60,136 +60,215 @@ % __ -% Jean-Yves Tinevez - 2016 +% Jean-Yves Tinevez & contributors - 2026 -%% Import the XPath classes. - import javax.xml.xpath.* %% Constants definition. - TRACKMATE_ELEMENT = 'TrackMate'; TRACK_ID_ATTRIBUTE = 'TRACK_ID'; TRACK_NAME_ATTRIBUTE = 'name'; SPOT_SOURCE_ID_ATTRIBUTE = 'SPOT_SOURCE_ID'; SPOT_TARGET_ID_ATTRIBUTE = 'SPOT_TARGET_ID'; %% Open file - + global isNotFirst xmlDoc %#ok + if isNotFirst + % Being called by other function + willClear = false; + else + isNotFirst = true; + willClear = true; + end + % We'll call trackmateFeatureDeclarations() to fill in table properties + % no matter what, so let's reuse that one's validation function. try - xmlDoc = xmlread( filePath ); - catch - error('Failed to read XML file %s.',filePath); + [ ~, ef ] = trackmateFeatureDeclarations( filePath ); + catch ME + throw(ME) end - xmlRoot = xmlDoc.getFirstChild(); - if ~strcmp(xmlRoot.getTagName, TRACKMATE_ELEMENT) - error('MATLAB:trackMateGraph:BadXMLFile', ... - 'File does not seem to be a proper TrackMate file.') - end - - - %% XPath initialization. - factory = XPathFactory.newInstance; - xPath = factory.newXPath; + rootObj = xmlDoc.getDocumentElement; + modelNodes = rootObj.getElementsByTagName('Model'); %% Retrieve edge feature list if nargin < 2 || isempty( featureList ) - xPathEdgeFilter = xPath.compile('//Edge'); - edgeNode = xPathEdgeFilter.evaluate(xmlDoc, XPathConstants.NODE ); - featureList = getEdgeFeatureList( edgeNode ); + % //Edge. Why not full path? + edgeList = rootObj.getElementsByTagName( 'Edge' ); + if edgeList.Length > 0 + attrMap = edgeList.node(1).getAttributes; + nFeatures = attrMap.Length; + featureList = cell(nFeatures, 1); + for k = 1 : nFeatures + featureList{k} = attrMap.item(k-1).Name; + end + else + featureList = {}; + end end % Add spot source and target, whether they are here or not. - featureList = union( SPOT_TARGET_ID_ATTRIBUTE, featureList, 'stable' ); - featureList = union( SPOT_SOURCE_ID_ATTRIBUTE, featureList, 'stable' ); + frontOfList = { SPOT_SOURCE_ID_ATTRIBUTE; SPOT_TARGET_ID_ATTRIBUTE }; + featureList = union(frontOfList, featureList, 'stable'); nFeatures = numel( featureList ); %% XPath to retrieve filtered track IDs. + % Prepare a map: trackName -> edge table. + trackMap = containers.Map('KeyType', 'char', 'ValueType', 'any'); - xPathFTrackFilter = xPath.compile('//Model/FilteredTracks/TrackID'); - fTrackNodeList = xPathFTrackFilter.evaluate(xmlDoc, XPathConstants.NODESET); - nFTracks = fTrackNodeList.getLength(); - - fTrackIDs = NaN( nFTracks, 1); - for i = 1 : nFTracks - fTrackIDs( i ) = str2double( fTrackNodeList.item( i-1 ).getAttribute( TRACK_ID_ATTRIBUTE ) ); + % XPath: //Model/FilteredTracks/TrackID/@TrackID + fTracks = zeros(rootObj.getElementsByTagName('TrackID').Length, 1); + iTracks = 0; + for j = 1:modelNodes.Length + node = modelNodes.node(j); + % Model level + fTNode = node.getFirstElementChild; + while ~isempty(fTNode) + if strcmp('FilteredTracks', fTNode.TagName) + % FilteredTracks level + tIDNode = fTNode.getFirstElementChild; + while ~isempty(tIDNode) + if strcmp('TrackID', tIDNode.TagName) + % Found TrackID node + iTracks = iTracks + 1; + fTracks(iTracks) = ... + str2double(tIDNode.getAttribute(TRACK_ID_ATTRIBUTE)); + end + tIDNode = tIDNode.getNextElementSibling; + end + end + fTNode = fTNode.getNextElementSibling; + end + end + + fTracks(iTracks+1:end) = []; + + if isempty(fTracks) + % No selected track, return empty map + if willClear + clear global isNotFirst xmlDoc xmlDocFileName + end + return end %% XPath to retrieve filtered track elements. - - xPathTrackFilter = xPath.compile('//Model/AllTracks/Track'); - trackNodeList = xPathTrackFilter.evaluate(xmlDoc, XPathConstants.NODESET); - nTracks = trackNodeList.getLength(); - - % Prepare a map: trackName -> edge table. - trackMap = containers.Map('KeyType', 'char', 'ValueType', 'any'); - - xPathEdgeFilter = xPath.compile('./Edge'); - for i = 1 : nTracks - - trackNode = trackNodeList.item( i-1 ); - trackID = str2double( trackNode.getAttribute( TRACK_ID_ATTRIBUTE ) ); - trackName = char( trackNode.getAttribute( TRACK_NAME_ATTRIBUTE ) ); - - if any( trackID == fTrackIDs ) - - edgeNodeList = xPathEdgeFilter.evaluate( trackNode, XPathConstants.NODESET ); - nEdges = edgeNodeList.getLength(); - features = NaN( nEdges, nFeatures ); - - % Read all edge nodes. - for k = 1 : nEdges - node = edgeNodeList.item( k-1 ); - for j = 1 : nFeatures - features( k, j ) = str2double( node.getAttribute( featureList{ j } ) ); - end + % Read Tracks table + neTracks = rootObj.getElementsByTagName('Track').Length; + names = cell(neTracks, 1); + tracks = zeros(neTracks, 1); + iTracks = 0; + for j = 1 : modelNodes.Length + node = modelNodes.node(j); + % Model level + aTNode = node.getFirstElementChild; + while ~isempty(aTNode) + if strcmp('AllTracks', aTNode.TagName) + % AllTracks level + tNode = aTNode.getFirstElementChild; + while ~isempty(tNode) + if strcmp('Track', tNode.TagName) + % Track level + iTracks = iTracks + 1; + names{iTracks} = tNode.getAttribute(TRACK_NAME_ATTRIBUTE); + tracks(iTracks) = str2double(tNode.getAttribute(TRACK_ID_ATTRIBUTE)); end - - % Create table. - edgeTable = table(); - for j = 1 : nFeatures - edgeTable.( featureList{ j } ) = features( :, j ); + tNode = tNode.getNextElementSibling; end - + end + aTNode = aTNode.getNextElementSibling; + end + end + names(iTracks+1:neTracks) = []; + tracks(iTracks+1:neTracks) = []; + + % Find the selected Track IDs and cache the result. + whichSel = ismember( tracks, fTracks); + + % Prepare metadata once + if ~isempty(whichSel) + nVNames = numel( featureList ); + vDescriptions = cell( nVNames, 1); + vUnits = cell( nVNames, 1); + + for l = 1 : nVNames + vn = featureList{ l }; + vDescriptions{ l } = ef( vn ).name; + vUnits{ l } = ef( vn ).units; + end + else + % None of the selected tracks were found, return empty map. + if willClear + clear global isNotFirst xmlDoc xmlDocFileName + end + return + end + + % Current track No. + iTracks = 0; + + % XPath: (//Model/AllTracks/Track)/Edge + for j = 1 : modelNodes.Length + node = modelNodes.node(j); + % Model level + aTNode = node.getFirstElementChild; + while ~isempty(aTNode) + if strcmp('AllTracks', aTNode.TagName) + % AllTracks level + tNode = aTNode.getFirstElementChild; + while ~isempty(tNode) + if strcmp('Track', tNode.TagName) + % Track level + iTracks = iTracks + 1; + if whichSel(iTracks) + % Create table. + edgeTable = walkAndMakeTable(tNode); % Set table metadata. - edgeTable.Properties.DimensionNames = { 'Edge', 'Feature' }; - - vNames = edgeTable.Properties.VariableNames; - nVNames = numel( vNames ); - vDescriptions = cell( nVNames, 1); - vUnits = cell( nVNames, 1); - - [ ~, ef ] = trackmateFeatureDeclarations( filePath ); - for l = 1 : nVNames - vn = vNames{ l }; - vDescriptions{ l } = ef( vn ).name; - vUnits{ l } = ef( vn ).units; + edgeTable.Properties.DimensionNames = { 'Edge', 'Feature' }; + edgeTable.Properties.VariableDescriptions = vDescriptions; + edgeTable.Properties.VariableUnits = vUnits; + + trackMap( names{iTracks} ) = edgeTable; + end + end + tNode = tNode.getNextElementSibling; end - edgeTable.Properties.VariableDescriptions = vDescriptions; - edgeTable.Properties.VariableUnits = vUnits; - - trackMap( trackName ) = edgeTable; - end + aTNode = aTNode.getNextElementSibling; + end + end + if willClear + clear global isNotFirst xmlDoc xmlDocFileName end %% Subfunction. - function featureList = getEdgeFeatureList(node) - - attribute_map = node.getAttributes; - n_attributes = attribute_map.getLength; - - featureList = cell(n_attributes, 1); - index = 1; - for ii = 1 : n_attributes - - namel = node.getAttributes.item(ii-1).getName; - featureList{index} = char(namel); - index = index + 1; - + function edgeTable = walkAndMakeTable(trackNode) + % Assuming every child element is an ... + neEdges = trackNode.getChildElementCount; + holders = cell(1, nFeatures); + for i = 1 : nFeatures + holders{i} = zeros(neEdges, 1); end + nEdges = 0; + edgeNode = trackNode.getFirstElementChild; + while ~isempty(edgeNode) + if strcmp('Edge', edgeNode.TagName) + nEdges = nEdges+1; + for i = 1 : nFeatures + holders{i}(nEdges) = str2double(edgeNode.getAttribute(featureList{i})); + end + end + edgeNode = edgeNode.getNextElementSibling; + end + + % And trim the end if that turns out to be false + if nEdges < neEdges + for i = 1 : nFeatures + holders{i}(nEdges+1:end) = []; + end + end + + edgeTable = table(holders{:}, 'VariableNames', featureList); end end \ No newline at end of file diff --git a/scripts/trackmateFeatureDeclarations.m b/scripts/trackmateFeatureDeclarations.m index b066b547a..1f3206aaa 100644 --- a/scripts/trackmateFeatureDeclarations.m +++ b/scripts/trackmateFeatureDeclarations.m @@ -39,13 +39,9 @@ % units: 'pixels' % __ -% Jean-Yves Tinevez - 2016 +% Jean-Yves Tinevez & contributors - 2026 - %% Import the XPath classes. - import javax.xml.xpath.* - - %% Constants definition. TRACKMATE_ELEMENT = 'TrackMate'; SPATIAL_UNITS_ATTRIBUTE = 'spatialunits'; @@ -58,86 +54,149 @@ %% Open and check XML. - - try - xmlDoc = xmlread(filePath); - catch - error('Failed to read XML file %s.',filePath); + % Parsing a large file takes time. Cache the document until return. + global isNotFirst xmlDoc xmlDocFileName %#ok + if isNotFirst + % Being called by other function + willClear = false; + else + isNotFirst = true; + willClear = true; end - xmlRoot = xmlDoc.getFirstChild(); - if ~strcmp(xmlRoot.getTagName, TRACKMATE_ELEMENT) + % Either being called by user, or being called by other functions and + % is the first run. Or somehow was used to work on another file. + if willClear || isempty(xmlDoc) || ~strcmp(xmlDocFileName, filePath) + try + xmlDoc = matlab.io.xml.dom.Parser().parseFile( filePath ); + catch ME + switch ME.identifier + case 'MATLAB:UndefinedFunction' + error("Your MATLAB is too old (pre-R2021a) to run this script.") + otherwise + % Attach the error to facilitate debugging. + error(ME.identifier, 'Failed to read XML file %s.',filePath); + end + end + xmlDocFileName = filePath; + end + + rootNode = xmlDoc.getDocumentElement; + if isempty(rootNode) || ~strcmp(TRACKMATE_ELEMENT, rootNode.TagName) error('MATLAB:trackMateGraph:BadXMLFile', ... 'File does not seem to be a proper TrackMate file.') end - factory = XPathFactory.newInstance; - xpath = factory.newXPath; + modelNode = rootNode.getFirstElementChild; + modelFound = false; + while ~isempty(modelNode) + if strcmp('Model', modelNode.TagName) + modelFound = true; + break + end + modelNode = modelNode.getNextElementSibling; + end + + if ~modelFound + error('MATLAB:trackMateGraph:BadXMLFile', ... + 'File does not seem to contain a valid Model element.') + end %% Retrieve physical units. - modelPath = xpath.compile('/TrackMate/Model'); - modelNode = modelPath.evaluate(xmlRoot, XPathConstants.NODESET).item(0); - spaceUnits = char( modelNode.getAttribute( SPATIAL_UNITS_ATTRIBUTE ) ); - timeUnits = char( modelNode.getAttribute( TIME_UNITS_ATTRIBUTE ) ); + % matlab.io.xml.dom.Element.getAttribute() returns character arrays + spaceUnits = modelNode.getAttribute( SPATIAL_UNITS_ATTRIBUTE ); + timeUnits = modelNode.getAttribute( TIME_UNITS_ATTRIBUTE ); %% XPath to retrieve spot feature declarations. - spotFeatureFilter = xpath.compile('/TrackMate/Model/FeatureDeclarations/SpotFeatures/Feature'); - spotFeatureNodes = spotFeatureFilter.evaluate(xmlDoc, XPathConstants.NODESET); - nSpotFeatureNodes = spotFeatureNodes.getLength(); - - sf = containers.Map(); - for i = 1 : nSpotFeatureNodes - f = readFeature( spotFeatureNodes.item( i-1 ), spaceUnits, timeUnits ); - sf( f.key ) = f; - end + % /TrackMate/Model/FeatureDeclarations/EdgeFeatures/Feature + sf = makeFeatureTable('SpotFeatures', modelNode); + sf = transformFeatureTable(sf, spaceUnits, timeUnits); %% XPath to retrieve edge feature declarations. - edgeFeatureFilter = xpath.compile('/TrackMate/Model/FeatureDeclarations/EdgeFeatures/Feature'); - edgeFeatureNodes = edgeFeatureFilter.evaluate(xmlDoc, XPathConstants.NODESET); - nEdgeFeatureNodes = edgeFeatureNodes.getLength(); - - ef = containers.Map(); - for i = 1 : nEdgeFeatureNodes - f = readFeature( edgeFeatureNodes.item( i-1 ), spaceUnits, timeUnits ); - ef( f.key ) = f; + if nargout >= 2 + % /TrackMate/Model/FeatureDeclarations/EdgeFeatures/Feature + ef = makeFeatureTable('EdgeFeatures', modelNode); + ef = transformFeatureTable(ef, spaceUnits, timeUnits); end %% XPath to retrieve track feature declarations. - trackFeatureFilter = xpath.compile('/TrackMate/Model/FeatureDeclarations/TrackFeatures/Feature'); - trackFeatureNodes = trackFeatureFilter.evaluate(xmlDoc, XPathConstants.NODESET); - nTrackFeatureNodes = trackFeatureNodes.getLength(); + if nargout >= 3 + % /TrackMate/Model/FeatureDeclarations/TrackFeatures/Feature + tf = makeFeatureTable('TrackFeatures', modelNode); + tf = transformFeatureTable(tf, spaceUnits, timeUnits); + end - tf = containers.Map(); - for i = 1 : nTrackFeatureNodes - f = readFeature( trackFeatureNodes.item( i-1 ), spaceUnits, timeUnits ); - tf( f.key ) = f; + if willClear + clear global isNotFirst xmlDoc xmlDocFileName end %% Subfunctions. - function f = readFeature(featureNode, spaceUnits, timeUnits) - - key = char( featureNode.getAttribute( FEATURE_KEY_ATTRIBUTE ) ); - name = char( featureNode.getAttribute( FEATURE_NAME_ATTRIBUTE ) ); - shortName = char( featureNode.getAttribute( FEATURE_SHORTNAME_ATTRIBUTE ) ); - dimension = char( featureNode.getAttribute( FEATURE_DIMENSION_ATTRIBUTE ) ); - isInt = strcmp( 'true', char( featureNode.getAttribute( FEATURE_ISINT_ATTRIBUTE ) ) ); - units = determineUnits( dimension, spaceUnits, timeUnits ); - - f = struct(); - f.key = key; - f.name = name; - f.shortName = shortName; - f.dimension = dimension; - f.isInt = isInt; - f.units = units; - + function ft = makeFeatureTable(featName, modelNode) + prevW = warning('off', 'MATLAB:table:PreallocateCharWarning'); + ft = table( 'Size', [0 5], ... + 'VariableNames', {'key' 'name' 'shortName' 'dimension' 'isInt'}, ... + 'VariableTypes', {'char' 'char' 'char' 'char' 'logical'}); + + declNode = modelNode.getFirstElementChild; + while ~isempty(declNode) + if strcmp('FeatureDeclarations', declNode.TagName) + % FeatureDeclarations level + fDNode = declNode.getFirstElementChild; + while ~isempty(fDNode) + if strcmp(featName, fDNode.TagName) + % featName level + neFeatures = fDNode.getChildElementCount; + key = cell(neFeatures, 1); + name = cell(neFeatures, 1); + shortName = cell(neFeatures, 1); + dimension = cell(neFeatures, 1); + isInt = false(neFeatures, 1); + + iFeat = 0; + featNode = fDNode.getFirstElementChild; + while ~isempty(featNode) + if strcmp('Feature', featNode.TagName) + % Feature node + iFeat = iFeat+1; + key{iFeat} = featNode.getAttribute(FEATURE_KEY_ATTRIBUTE); + name{iFeat} = featNode.getAttribute(FEATURE_NAME_ATTRIBUTE); + shortName{iFeat} = featNode.getAttribute(FEATURE_SHORTNAME_ATTRIBUTE); + dimension{iFeat} = featNode.getAttribute(FEATURE_DIMENSION_ATTRIBUTE); + isInt(iFeat) = strcmp('true', featNode.getAttribute(FEATURE_ISINT_ATTRIBUTE)); + end + featNode = featNode.getNextElementSibling; + end + + t = table(key, name, shortName, dimension, isInt); + if iFeat < neFeatures + t(iFeat+1:end, :) = []; + end + ft = vertcat(ft, t); %#ok + end + fDNode = fDNode.getNextElementSibling; + end + end + declNode = declNode.getNextElementSibling; + end + + warning(prevW); + end + + % Fill in the Units, and transform into Map + function featureMap = transformFeatureTable(featureTable, spaceUnits, timeUnits) + units = cellfun(@(dim)determineUnits(dim, spaceUnits, timeUnits), ... + featureTable.dimension, 'UniformOutput', false); + featureTable = addvars(featureTable, units, 'NewVariableNames', 'units'); + + featureStruct = table2struct(featureTable); + featureMap = containers.Map({featureStruct.key}.', num2cell(featureStruct)); end function units = determineUnits( dimension, spaceUnits, timeUnits ) diff --git a/scripts/trackmateGraph.m b/scripts/trackmateGraph.m index 5f4b2a77e..d0d93a2d2 100644 --- a/scripts/trackmateGraph.m +++ b/scripts/trackmateGraph.m @@ -58,7 +58,7 @@ % >> axis equal % __ -% Jean-Yves Tinevez - 2016 - 2024 +% Jean-Yves Tinevez & contributors - 2026 %% Constants definition. @@ -78,6 +78,14 @@ end end + global isNotFirst %#ok + if isNotFirst + willClear = false; + else + isNotFirst = true; + willClear = true; + end + %% Import spot table. if verbose @@ -128,14 +136,16 @@ t = cell2mat( values( spotIDMap, num2cell(targetID) ) ); EndNodes = [ s t ]; - nodeTable = table( EndNodes ); - nt = horzcat( nodeTable, edgeTable ); + edgeTable = addvars(edgeTable, EndNodes, 'Before', 1, 'NewVariableNames', 'EndNodes'); - G = digraph( nt, spotTable ); + G = digraph( edgeTable, spotTable ); if verbose fprintf('Done in %.1f s.\n', toc) end + if willClear + clear global isNotFirst xmlDoc xmlDocFileName + end -end +end \ No newline at end of file diff --git a/scripts/trackmateImageCalibration.m b/scripts/trackmateImageCalibration.m index 317692883..7b2229621 100644 --- a/scripts/trackmateImageCalibration.m +++ b/scripts/trackmateImageCalibration.m @@ -25,13 +25,21 @@ % in physical units % __ -% Jean-Yves Tinevez - 2016 +% Jean-Yves Tinevez & contributors - 2026 %% Open XML file + try + tree = matlab.io.xml.dom.Parser().parseFile(path); + root = tree.getDocumentElement; + catch ME + switch ME.identifier + case 'MATLAB:UndefinedFunction' + error("Your MATLAB is too old (pre-R2021a) to run this script.") + otherwise + throw(ME) + end + end - tree = xmlread(path); - root = tree.getFirstChild(); - %% Prepare dim strings dimensionNames = { 'x', 'y', 'z', 't' }; @@ -41,6 +49,7 @@ %% Collect basic settings. + % //Settings[1]//BasicSettings[1] settings = root.getElementsByTagName('Settings'); settings = settings.item(0); bs = settings.getElementsByTagName('BasicSettings'); @@ -48,6 +57,7 @@ %% Collect image settings. + % //ImageData[1] id = root.getElementsByTagName('ImageData'); id = id.item(0); @@ -68,6 +78,7 @@ %% Get physical units from model element. + % //Model[1] model = root.getElementsByTagName('Model'); model = model.item(0); @@ -77,7 +88,7 @@ dim = dimensionNames{i}; - cal.(dim).units = char(model.getAttribute(unitsNames{i})); + cal.(dim).units = model.getAttribute(unitsNames{i}); end diff --git a/scripts/trackmateSpots.m b/scripts/trackmateSpots.m index 8a0ae5b31..7d3850420 100644 --- a/scripts/trackmateSpots.m +++ b/scripts/trackmateSpots.m @@ -80,140 +80,191 @@ % __ -% Jean-Yves Tinevez - 2016 - 2024 +% Jean-Yves Tinevez & contributors - 2026 - %% Import the XPath classes. - import javax.xml.xpath.* - %% Constants definition. - TRACKMATE_ELEMENT = 'TrackMate'; SPOT_ID_ATTRIBUTE = 'ID'; SPOT_NAME_ATTRIBUTE = 'name'; ROI_N_POINTS_ATTTRIBUTE = 'ROI_N_POINTS'; %% Open file. - - try - xmlDoc = xmlread(filePath); - catch - error('Failed to read XML file %s.',filePath); + global isNotFirst xmlDoc %#ok + if isNotFirst + % Being called by other function + willClear = false; + else + isNotFirst = true; + willClear = true; end - xmlRoot = xmlDoc.getFirstChild(); - if ~strcmp(xmlRoot.getTagName, TRACKMATE_ELEMENT) - error('MATLAB:trackMateGraph:BadXMLFile', ... - 'File does not seem to be a proper TrackMate file.') + % We'll call trackmateFeatureDeclarations() to fill in table properties + % no matter what, so let's reuse that one's validation function. + try + fs = trackmateFeatureDeclarations( filePath ); + catch ME + rethrow(ME) end - - - %% XPath to retrieve spot nodes. - - % Use XPath to retrieve all visible spots. - factory = XPathFactory.newInstance; - xPath = factory.newXPath; - xPathFilter = xPath.compile('//Model/AllSpots/SpotsInFrame/Spot[@VISIBILITY=1]'); - nodeList = xPathFilter.evaluate(xmlDoc, XPathConstants.NODESET); + rootObj = xmlDoc.getDocumentElement; + modelNodes = rootObj.getElementsByTagName('Model'); %% Retrieve spot feature list. if nargin < 2 || isempty( featureList ) - featureList = getSpotFeatureList(nodeList.item(0)); + spotFound = false; + % XPath: (TrackMate/Model/AllSpots/SpotsInFrame/Spot)[1] + for j = 1:modelNodes.Length + aSNode = modelNodes.node(j).getFirstElementChild; + while ~spotFound && ~isempty(aSNode) + if strcmp ('AllSpots', aSNode.TagName) + % AllSpots level + sIFNode = aSNode.getFirstElementChild; + while ~spotFound && ~isempty(sIFNode) + if strcmp('SpotsInFrame', sIFNode.TagName) + spotNode = sIFNode.getFirstElementChild; + while ~spotFound && ~isempty(spotNode) + if strcmp('Spot', spotNode.TagName) + spotFound = true; + attrMap = spotNode.getAttributes; + nFeatures = attrMap.Length; + featureList = cell(attrMap.Length, 1); + for k = 1:nFeatures + featureList{k} = attrMap.item(k-1).Name; + end + break + end + spotNode = spotNode.getNextElementSibling; + end + end + sIFNode = sIFNode.getNextElementSibling; + end + end + aSNode = aSNode.getNextElementSibling; + end + end + if ~spotFound + featureList = {}; + end end % Remove ID and name, because we will get them anyway. featureList = setdiff( featureList, SPOT_ID_ATTRIBUTE ); featureList = setdiff( featureList, SPOT_NAME_ATTRIBUTE ); + featureList = [{SPOT_ID_ATTRIBUTE; SPOT_NAME_ATTRIBUTE}; featureList]; n_features = numel( featureList ); %% Get filtered spot IDs. + % Assuming every Spot node resides in the right place. + neSpots = 0; + for j = 1:modelNodes.Length + neSpots = neSpots + modelNodes.node(j).getElementsByTagName('Spot').Length; + end + % Prepare holders. - nSpots = nodeList.getLength(); - ID = NaN( nSpots, 1 ); - name = cell( nSpots, 1); - features = NaN( nSpots, n_features ); - rois = cell( nSpots, 1); + holder = cell(1, n_features); + holder{1} = zeros(neSpots, 1); + holder{2} = cell(neSpots, 1); + for k = 3:n_features + holder{k} = zeros(neSpots, 1); + end + + % Read ROI coords if it's requested + if nargout >= 3 + willReadROIs = true; + rois = cell(neSpots, 1); + else + willReadROIs = false; + end % Read all spot nodes. - for i = 1 : nSpots - node = nodeList.item( i-1 ); - ID( i ) = str2double( node.getAttribute( SPOT_ID_ATTRIBUTE ) ); - name{ i } = char( node.getAttribute( SPOT_NAME_ATTRIBUTE ) ); - for j = 1 : n_features - features( i, j ) = str2double( node.getAttribute( featureList{ j } ) ); - end - - % Read ROI coords if it's there. - if nargout >= 3 - coords_str = node.getTextContent(); - if ~isempty( coords_str ) - A = sscanf(string(coords_str),'%f'); - n_points = numel(A) / 2; - A = reshape( A, 2, n_points )'; - rois{i} = A; + nSpots = 0; + % XPath: //Model/AllSpots/SpotsInFrame/Spot + for j = 1:modelNodes.Length + aSNode = modelNodes.node(j).getFirstElementChild; + while ~isempty(aSNode) + if strcmp ('AllSpots', aSNode.TagName) + % AllSpots level + sIFNode = aSNode.getFirstElementChild; + while ~isempty(sIFNode) + if strcmp('SpotsInFrame', sIFNode.TagName) + % SpotsInFrame level. + spotNode = sIFNode.getFirstElementChild; + while ~isempty(spotNode) + if strcmp('Spot', spotNode.TagName) + nSpots = nSpots + 1; + holder{1}(nSpots) = str2double(spotNode.getAttribute(featureList{1})); + holder{2}{nSpots} = spotNode.getAttribute(featureList{2}); + for k = 3:nFeatures + holder{k}(nSpots) = str2double(spotNode.getAttribute(featureList{k})); + end + + if willReadROIs + coords_str = spotNode.TextContent; + if ~isempty(coords_str) + A = sscanf( coords_str, '%f' ); + A = reshape( A, 2, [] ).'; + rois{nSpots} = A; + end + end + end + spotNode = spotNode.getNextElementSibling; + end end + sIFNode = sIFNode.getNextElementSibling; + end + end + aSNode = aSNode.getNextElementSibling; + end + end + + if nSpots ~= neSpots + for k = 1 : nFeatures + holder{k}(nSpots+1:end) = []; + end + if willReadROIs + rois(nSpots+1:end) = []; end end % Create table. - spotTable = table(); - spotTable.( SPOT_ID_ATTRIBUTE ) = ID; - spotTable.( SPOT_NAME_ATTRIBUTE ) = name; - for j = 1 : n_features - spotTable.( featureList{ j } ) = features( :, j ); - end + spotTable = table(holder{:}, 'VariableNames', featureList); % Set table metadata. spotTable.Properties.DimensionNames = { 'Spot', 'Feature' }; - vNames = spotTable.Properties.VariableNames; - nVNames = numel( vNames ); - vDescriptions = cell( nVNames, 1); - vUnits = cell( nVNames, 1); + [vDescriptions,vUnits] = cellfun(@determineDescriptions, ... + featureList, 'UniformOutput', false); - fs = trackmateFeatureDeclarations( filePath ); - for k = 1 : nVNames - vn = vNames{ k }; - if strcmp( SPOT_ID_ATTRIBUTE, vn ) - vDescriptions{ k } = 'Spot ID'; - vUnits{ k } = ''; - elseif strcmp( SPOT_NAME_ATTRIBUTE, vn ) - vDescriptions{ k } = 'Spot name'; - vUnits{ k } = ''; - elseif strcmp( ROI_N_POINTS_ATTTRIBUTE, vn ) - vDescriptions{ k } = 'ROI N points'; - vUnits{ k } = ''; - else - vDescriptions{ k } = fs( vn ).name; - vUnits{ k } = fs( vn ).units; - end - end spotTable.Properties.VariableDescriptions = vDescriptions; spotTable.Properties.VariableUnits = vUnits; % Generate map ID -> table row number. - spotIDMap = containers.Map( ID, 1 : nSpots, ... + if nargout >= 2 + spotIDMap = containers.Map( spotTable.ID, 1 : nSpots, ... 'UniformValues', true); + end + if willClear + clear global isNotFirst xmlDoc xmlDocFileName + end + %% Subfunction. - function featureList = getSpotFeatureList(node) - - attribute_map = node.getAttributes; - nAttributes = attribute_map.getLength; - - featureList = cell(nAttributes - 1, 1); % -1 for the spot name, which we do not take - index = 1; - for ii = 1 : nAttributes - - namel = node.getAttributes.item(ii-1).getName; - if strcmp(namel, SPOT_NAME_ATTRIBUTE) - continue; - end - featureList{index} = char(namel); - index = index + 1; - + function [desc, unit] = determineDescriptions( varName ) + switch ( varName ) + case SPOT_ID_ATTRIBUTE + desc = 'Spot ID'; + unit = ''; + case SPOT_NAME_ATTRIBUTE + desc = 'Spot name'; + unit = ''; + case ROI_N_POINTS_ATTTRIBUTE + desc = 'ROI N points'; + unit = ''; + otherwise + desc = fs(varName).name; + unit = fs(varName).units; end end