diff --git a/+siibra/+internal/API.m b/+siibra/+internal/API.m index d9da4ce..7473608 100644 --- a/+siibra/+internal/API.m +++ b/+siibra/+internal/API.m @@ -2,18 +2,19 @@ % The API class holds all the api calls in one place properties (Constant=true) - Endpoint = "https://siibra-api-stable.apps.hbp.eu/v1_0/" - EndpointV2 = "https://siibra-api-stable.apps.hbp.eu/v2_0" - EndpointV3 = "https://siibra-api-stable.apps.hbp.eu/v3_0" + %Endpoint = "https://siibra-api-stable.apps.hbp.eu/v1_0/" + %EndpointV2 = "https://siibra-api-stable.apps.hbp.eu/v2_0" + EndpointV3 = "https://siibra-api-stable.apps.hbp.eu/v3_0/" + %EndpointV3 = "https://siibra-api-prod.apps.tc.humanbrainproject.eu/v3_0/" end methods (Static) - function link = absoluteLink(relativeLink) - link = siibra.internal.API.Endpoint + relativeLink; - end - function link = absoluteLinkV2(relativeLink) - link = siibra.internal.API.EndpointV2 + relativeLink; - end + %function link = absoluteLink(relativeLink) + % link = siibra.internal.API.Endpoint + relativeLink; + %end + %function link = absoluteLinkV2(relativeLink) + % link = siibra.internal.API.EndpointV2 + relativeLink; + %end function link = absoluteLinkV3(relativeLink) link = siibra.internal.API.EndpointV3 + relativeLink; end @@ -32,21 +33,63 @@ absoluteLink, ... options); end - function atlases = atlases() - absoluteLink = siibra.internal.API.absoluteLink("atlases"); - atlases = siibra.internal.API.doWebreadWithLongTimeout( ... + + function parameterString = parametersToString(parameters) + parameterString = "?" + strjoin(parameters, "&"); + end + + function items = collectItemsAcrossPages(absoluteLink, parameters) + if nargin < 2 + parameters = []; + end + + first_result = siibra.internal.API.doWebreadWithLongTimeout(absoluteLink + ... + siibra.internal.API.parametersToString([parameters, "page=1"])); + items = first_result.items; + page = first_result.page; + pages = first_result.pages; + while page < pages + page = page + 1; + next_result = siibra.internal.API.doWebreadWithLongTimeout(absoluteLink ... + + siibra.internal.API.parametersToString([parameters, "page=" + page])); + items = [items; next_result.items]; + end + + end + + function atlasJsons = atlases() + absoluteLink = siibra.internal.API.absoluteLinkV3("atlases"); + atlasJsons = siibra.internal.API.collectItemsAcrossPages( ... absoluteLink); end - function absoluteLink = regionInfoForSpace(atlasId, parcellationId, regionName, spaceId) - relativeLink = "atlases/" + atlasId + ... - "/parcellations/" + parcellationId + ... - "/regions/" + regionName + ... - "?space_id=" + spaceId; - absoluteLink = siibra.internal.API.absoluteLink(relativeLink); + + function spaceJson = space(spaceId) + absoluteLink = siibra.internal.API.absoluteLinkV3("spaces/" + spaceId); + spaceJson = siibra.internal.API.doWebreadWithLongTimeout(absoluteLink); + end + + function parcellationJson = parcellation(parcellationId) + absoluteLink = siibra.internal.API.absoluteLinkV3("parcellations/" + parcellationId); + parcellationJson = siibra.internal.API.doWebreadWithLongTimeout(absoluteLink); + end + + function regionsJson = regions(parcellationId) + absoluteLink = siibra.internal.API.absoluteLinkV3("regions"); + parameters = ["parcellation_id=" + parcellationId]; + regionsJson = siibra.internal.API.collectItemsAcrossPages( ... + absoluteLink, parameters); + end + + function absoluteLink = regionInfoForSpace(parcellationId, regionName, spaceId) + relativeLink = "map/statistical_map.info.json" + ... + "?parcellation_id=" + parcellationId + ... + "®ion_id=" + regionName + ... + "&space_id=" + spaceId; + absoluteLink = siibra.internal.API.absoluteLinkV3(relativeLink); end function absoluteLink = regionMap(parcellationId, regionName, spaceId) - relativeLink = "/map/statistical_map.nii.gz" + ... + relativeLink = "map/statistical_map.nii.gz" + ... "?parcellation_id=" + parcellationId + ... "&space_id=" + spaceId + ... "®ion_id=" + regionName; @@ -59,7 +102,7 @@ parcellationId string regionName string = string.empty end - relativeLink = "/map/labelled_map.nii.gz?parcellation_id=" + parcellationId + ... + relativeLink = "map/labelled_map.nii.gz?parcellation_id=" + parcellationId + ... "&space_id=" + spaceId; if ~isempty(regionName) relativeLink = relativeLink + "®ion_id=" + regionName; @@ -67,18 +110,18 @@ absoluteLink = siibra.internal.API.absoluteLinkV3(relativeLink); end - function absoluteLink = templateForParcellationMap(spaceId, parcellationId) + function absoluteLink = templateForParcellationMap(parcellationId, spaceId) arguments - spaceId string parcellationId string + spaceId string end - relativeLink = "/map/resampled_template?parcellation_id=" + parcellationId + ... + relativeLink = "map/resampled_template?parcellation_id=" + parcellationId + ... "&space_id=" + spaceId; absoluteLink = siibra.internal.API.absoluteLinkV3(relativeLink); end function absoluteLink = featuresPageForParcellation(atlasId, parcellationId, page, size) - relativeLink = "/atlases/" + atlasId + ... + relativeLink = "atlases/" + atlasId + ... "/parcellations/" + parcellationId + ... "/features?page=" + page + ... "&size=" + size; @@ -114,19 +157,19 @@ end function absoluteLink = parcellationFeature(atlasId, parcellationId, featureId) % get specific feature by feature id. - relativeLink = "/atlases/" + atlasId + ... + relativeLink = "atlases/" + atlasId + ... "/parcellations/" + parcellationId + ... "/features/" + featureId; absoluteLink = siibra.internal.API.absoluteLinkV2(relativeLink); end function absoluteLink = featuresForRegion(atlasId, parcellationId, regionName) - relativeLink = "/atlases/" +atlasId + ... + relativeLink = "atlases/" +atlasId + ... "/parcellations/" + parcellationId + ... "/regions/" + regionName + "/features"; absoluteLink = siibra.internal.API.absoluteLinkV2(relativeLink); end function absoluteLink = regionFeature(atlasId, parcellationId, regionName, featureId) - relativeLink = "/atlases/" +atlasId + ... + relativeLink = "atlases/" +atlasId + ... "/parcellations/" + parcellationId + ... "/regions/" + regionName + ... "/features/" + featureId; diff --git a/+siibra/+items/+maps/ContinuousRegionMap.m b/+siibra/+items/+maps/ContinuousRegionMap.m index d84c537..b69b2b7 100644 --- a/+siibra/+items/+maps/ContinuousRegionMap.m +++ b/+siibra/+items/+maps/ContinuousRegionMap.m @@ -16,37 +16,6 @@ function obj = ContinuousRegionMap(region, space) obj.Region = region; obj.Space = space; - regionMapInfoJson = siibra.internal.API.doWebreadWithLongTimeout( ... - siibra.internal.API.regionInfoForSpace( ... - obj.Region.Parcellation.Atlas.Id, ... - obj.Region.Parcellation.Id, ... - obj.Region.Name, ... - obj.Space.Id ... - ) ... - ); - if ~regionMapInfoJson.hasRegionalMap - error('StatisticalMap:NotFound', "Region " + region.Name + " has no statistical map!") - end - datasetSpecs = regionMapInfoJson.x_dataset_specs; - if ~iscell(datasetSpecs) - datasetSpecs = num2cell(datasetSpecs); - end - datasetIndex = find( ... - cell2mat( ... - cellfun( ... - @(dataset) ... - isequal(dataset.x_type, 'minds/core/dataset/v1.0.0'), ... - datasetSpecs, ... - 'UniformOutput', false))); - if isempty(datasetIndex) - obj.Name = "Not available"; - obj.Description = "Not available"; - obj.DOI = string.empty; - else - obj.Name = datasetSpecs{datasetIndex}.name; - obj.Description = datasetSpecs{datasetIndex}.description; - obj.DOI = datasetSpecs{datasetIndex}.urls.doi; - end end function cachePath = get.CachePath(obj) diff --git a/+siibra/+items/Atlas.m b/+siibra/+items/Atlas.m index 6f3504a..dff46bf 100644 --- a/+siibra/+items/Atlas.m +++ b/+siibra/+items/Atlas.m @@ -8,16 +8,14 @@ end methods function atlas = Atlas(atlasJson) - atlas.Id = atlasJson.id; + atlas.Id = atlasJson.x_id; atlas.Name = atlasJson.name; % Spaces - spacesJson = webread(atlasJson.links.spaces.href); - atlas.Spaces = arrayfun(@(j) siibra.items.Space(j, atlas.Name), spacesJson); + atlas.Spaces = arrayfun(@(spaceRef) siibra.items.Space(siibra.internal.API.space(spaceRef.x_id), atlas.Name), atlasJson.spaces); % Parcellations - parcellationsJson = webread(atlasJson.links.parcellations.href); - atlas.Parcellations = arrayfun(@(json) siibra.items.Parcellation(json, atlas), parcellationsJson); + atlas.Parcellations = arrayfun(@(parcellationRef) siibra.items.Parcellation(siibra.internal.API.parcellation(parcellationRef.x_id), atlas), atlasJson.parcellations); end function parcellation = getParcellation(obj, parcellationNameQuery) diff --git a/+siibra/+items/Parcellation.m b/+siibra/+items/Parcellation.m index 01711ec..451baec 100644 --- a/+siibra/+items/Parcellation.m +++ b/+siibra/+items/Parcellation.m @@ -9,12 +9,13 @@ Modality (1, 1) string Description (1, 1) string RegionTree (1, 1) digraph + Regions (1, :) siibra.items.Region Spaces (1, :) siibra.items.Space end methods function parcellation = Parcellation(parcellationJson, atlas) - parcellation.Id = strcat(parcellationJson.id.kg.kgSchema, '/', parcellationJson.id.kg.kgId); + parcellation.Id = parcellationJson.x_id; parcellation.Name = parcellationJson.name; parcellation.Atlas = atlas; @@ -24,53 +25,59 @@ parcellation.Modality = parcellationJson.modality; end - % some parcellations do have a description - if ~ isempty(parcellationJson.infos) - parcellation.Description = parcellationJson.infos(1).description; + spaceIds = arrayfun(@(atlasVersion) atlasVersion.coordinateSpace, parcellationJson.brainAtlasVersions); + spaces = arrayfun(@(spaceId) atlas.Spaces([atlas.Spaces.Id] == spaceId.x_id), spaceIds); + if isempty(spaces) + parcellation.Spaces = siibra.items.Space.empty(); + else + parcellation.Spaces = spaces; end - - % link spaces from atlas - parcellation.Spaces = siibra.items.Space.empty; - % retrieve available spaces from atlas - for idx = 1:numel(parcellationJson.availableSpaces) - % store handle to space object - for atlasSpaceIndex = 1:numel(atlas.Spaces) - if isequal(atlas.Spaces(atlasSpaceIndex).Id, parcellationJson.availableSpaces(idx).id) - parcellation.Spaces(end +1) = atlas.Spaces(atlasSpaceIndex); - end + + regionsJson = siibra.internal.API.regions(parcellation.Id); + function result = getSrc(regionJson) + if isempty(regionJson.hasParent) + result = regionJson.x_id; + else + result = regionJson.hasParent.x_id; end + end - - % call api to get parcellation tree - regions = webread(parcellationJson.links.regions.href); - + % the api returns the same regions multiple times. so we filter + % them here. + [~, uniqueRegionIndices, ~] = unique({regionsJson.x_id}); + parcellation.Regions = arrayfun(@(regionJson) siibra.items.Region(regionJson.x_id, regionJson.name, parcellation), regionsJson(uniqueRegionIndices)); + srcIds = arrayfun(@(regionJson) getSrc(regionJson), regionsJson(uniqueRegionIndices), "UniformOutput",false); + regionIds = [parcellation.Regions.Id]; + src = arrayfun(@(srcId) find(regionIds == srcId), srcIds); + dst = 1:numel(src); + + nodeTable = table([parcellation.Regions.Id].', [parcellation.Regions.Name].', parcellation.Regions.', 'VariableNames', ["Name", "RegionName", "Region"]); % store graph - parcellation.RegionTree = siibra.items.Parcellation.createParcellationTree(parcellation, regions); + parcellation.RegionTree = digraph(src, dst, zeros(length(src), 1), nodeTable); end - function map = parcellationMap(obj, spaceName) - idx = siibra.internal.fuzzyMatching(spaceName, [obj.Spaces.Name]); - map = siibra.items.maps.ParcellationMap(obj, obj.Spaces(idx)); + function map = parcellationMap(obj, space) + map = siibra.items.maps.ParcellationMap(obj, space); end function regionNames = findRegion(obj, regionNameQuery) - regionNames = obj.RegionTree.Nodes(contains(obj.RegionTree.Nodes.Name, regionNameQuery), 1); + regionNames = obj.RegionTree.Nodes(contains(obj.RegionTree.Nodes.RegionName, regionNameQuery), 2); end function region = decodeRegion(obj, regionNameQuery) - index = siibra.internal.fuzzyMatching(regionNameQuery, [obj.RegionTree.Nodes.Name]); + index = siibra.internal.fuzzyMatching(regionNameQuery, [obj.RegionTree.Nodes.RegionName]); region = obj.RegionTree.Nodes.Region(index); end function region = getRegion(obj, regionNameQuery) nodeId = obj.RegionTree.findnode(regionNameQuery); region = obj.RegionTree.Nodes.Region(nodeId); end - function children = getChildRegions(obj, regionName) - nodeId = obj.RegionTree.findnode(regionName); + function children = getChildRegions(obj, region) + nodeId = obj.RegionTree.findnode(region.Id); childrenIds = obj.RegionTree.successors(nodeId); children = obj.RegionTree.Nodes.Region(childrenIds); end - function parentRegion = getParentRegion(obj, regionName) - nodeId = obj.RegionTree.findnode(regionName); + function parentRegion = getParentRegion(obj, region) + nodeId = obj.RegionTree.findnode(region.Id); parents = obj.RegionTree.predecessors(nodeId); assert(length(parents) == 1, "Expect just one parent in a tree structure"); parentId = parents(1); diff --git a/+siibra/+items/Region.m b/+siibra/+items/Region.m index 42cae6b..b241ee9 100644 --- a/+siibra/+items/Region.m +++ b/+siibra/+items/Region.m @@ -1,39 +1,16 @@ classdef Region < handle %REGION A region is a node in the RegionTree of the parcellation. properties + Id string Name string NormalizedName string Parcellation (1, :) siibra.items.Parcellation - Spaces (1, :) siibra.items.Space - Parent (1, 1) % Region - Children (1, :) % Region - IsLeaf logical end methods - function region = Region(name, parcellation, datasetSpecs) + function region = Region(id, name, parcellation) + region.Id = id; region.Name = name; region.Parcellation = parcellation; - spaces = siibra.items.Space.empty; - % parse datasetSpecs for this region - if ~isempty(datasetSpecs) - for i = 1:numel(datasetSpecs) - if iscell(datasetSpecs) - specs = datasetSpecs{i}; - else - specs = datasetSpecs(i); - end - if isfield(specs, "space_id") - for spaceIndex = 1:numel(parcellation.Spaces) - space = parcellation.Spaces(spaceIndex); - if specs.space_id == space.Id - spaces(end + 1) = space; - - end - end - end - end - end - region.Spaces = spaces; end function normalizedRegionName = get.NormalizedName(obj) normalizedRegionName = strrep(obj.Name, " ", ""); @@ -47,23 +24,21 @@ function support = doesRegionSupportSpace(obj, space) support = any(strcmp([obj.Spaces.Id], space.Id)); end - function children = get.Children(obj) - children = obj.Parcellation.getChildRegions(obj.Name); + function children = children(obj) + children = obj.Parcellation.getChildRegions(obj); end - function parent = get.Parent(obj) - parent = obj.Parcellation.getParentRegion(obj.Name); + function parent = parent(obj) + parent = obj.Parcellation.getParentRegion(obj); end - function isLeaf = get.IsLeaf(obj) - isLeaf = isempty(obj.Children); + function isLeaf = isLeaf(obj) + isLeaf = isempty(obj.children); end - function mask = getMask(obj, spaceName) - space = obj.matchAgainstSpacesParcellationSupports(spaceName); + function mask = getMask(obj, space) mask = siibra.items.maps.LabelledRegionMap(obj, space); end - function map = continuousMap(obj, spaceName) - space = obj.matchAgainstSpacesParcellationSupports(spaceName); - if obj.IsLeaf + function map = continuousMap(obj, space) + if obj.isLeaf map = siibra.items.maps.ContinuousRegionMap(obj, space); else error("continuous maps are supported on leafs only!"); @@ -102,16 +77,16 @@ end - function visualizeInTemplate(obj, spaceName, colormap_name) + function visualizeInTemplate(obj, space, colormap_name) arguments obj - spaceName string + space siibra.items.Space colormap_name string = "jet" end - space = obj.matchAgainstSpacesParcellationSupports(spaceName); + template = space.loadTemplateResampledForParcellation(obj.Parcellation).normalizedData(); - continuousMap = obj.continuousMap(spaceName).fetch().loadData(); + continuousMap = obj.continuousMap(space).fetch().loadData(); fig = uifigure; g = uigridlayout(fig, [2, 2]); diff --git a/+siibra/+items/Space.m b/+siibra/+items/Space.m index e01a6d7..90b4f33 100644 --- a/+siibra/+items/Space.m +++ b/+siibra/+items/Space.m @@ -4,43 +4,29 @@ Id (1, 1) string Name (1, 1) string NormalizedName (1, 1) string - TemplateURL (1, 1) string - Format (1, 1) string - VolumeType (1, 1) string AtlasName(1, 1) string end methods - function space = Space(atlasSpaceReferenceJson, atlasName) + function space = Space(spaceJson, atlasName) space.AtlasName = atlasName; - spaceJson = webread(atlasSpaceReferenceJson.links.self.href); - space.Id = spaceJson.id; - space.Name = spaceJson.name; - space.Format = spaceJson.type; - space.VolumeType = spaceJson.src_volume_type; - space.TemplateURL = spaceJson.links.templates.href; + space.Id = spaceJson.x_id; + space.Name = spaceJson.fullName; + end function normalizedName = get.NormalizedName(obj) normalizedName = strrep(obj.Name, " ", ""); end - function niftiImage = loadTemplate(obj) - cachedPath = siibra.internal.cache(obj.Name + ".nii", "template_cache"); - if ~isfile(cachedPath) - options = weboptions; - options.Timeout = 30; - websave(cachedPath, obj.TemplateURL, options); - end - niftiImage = siibra.items.NiftiImage(cachedPath); - end function niftiImage = loadTemplateResampledForParcellation(obj, parcellation) - cachedPath = siibra.internal.cache(obj.Name + "-" + parcellation.Name + ".nii.gz", "template_cache"); + cachedPath = siibra.internal.cache(obj.Name + ".nii.gz", "template_cache"); if ~isfile(cachedPath) siibra.internal.API.doWebsaveWithLongTimeout( ... cachedPath, ... - siibra.internal.API.templateForParcellationMap(obj.Id, parcellation.Id) ... - ) + siibra.internal.API.templateForParcellationMap(parcellation.Id, obj.Id) ... + ); end niftiImage = siibra.items.NiftiImage(cachedPath); end + end end \ No newline at end of file diff --git a/walkthrough.mlx b/walkthrough.mlx index 1b5b0b8..6c17e99 100644 Binary files a/walkthrough.mlx and b/walkthrough.mlx differ