siibra 0.4a33__py3-none-any.whl → 0.4a46__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of siibra might be problematic. Click here for more details.

Files changed (64) hide show
  1. siibra/VERSION +1 -1
  2. siibra/__init__.py +2 -0
  3. siibra/commons.py +53 -8
  4. siibra/configuration/configuration.py +21 -17
  5. siibra/configuration/factory.py +95 -19
  6. siibra/core/atlas.py +11 -8
  7. siibra/core/concept.py +41 -8
  8. siibra/core/parcellation.py +94 -43
  9. siibra/core/region.py +160 -187
  10. siibra/core/space.py +44 -39
  11. siibra/features/__init__.py +19 -19
  12. siibra/features/anchor.py +9 -6
  13. siibra/features/connectivity/__init__.py +0 -8
  14. siibra/features/connectivity/functional_connectivity.py +11 -3
  15. siibra/features/{basetypes → connectivity}/regional_connectivity.py +46 -33
  16. siibra/features/connectivity/streamline_counts.py +3 -2
  17. siibra/features/connectivity/streamline_lengths.py +3 -2
  18. siibra/features/{basetypes → dataset}/__init__.py +2 -0
  19. siibra/features/{external → dataset}/ebrains.py +3 -3
  20. siibra/features/feature.py +420 -0
  21. siibra/{samplers → features/image}/__init__.py +7 -1
  22. siibra/features/{basetypes/volume_of_interest.py → image/image.py} +12 -7
  23. siibra/features/{external/__init__.py → image/sections.py} +8 -5
  24. siibra/features/image/volume_of_interest.py +70 -0
  25. siibra/features/{cellular → tabular}/__init__.py +7 -11
  26. siibra/features/{cellular → tabular}/bigbrain_intensity_profile.py +5 -2
  27. siibra/features/{cellular → tabular}/cell_density_profile.py +6 -2
  28. siibra/features/{basetypes → tabular}/cortical_profile.py +48 -41
  29. siibra/features/{molecular → tabular}/gene_expression.py +5 -2
  30. siibra/features/{cellular → tabular}/layerwise_bigbrain_intensities.py +6 -2
  31. siibra/features/{cellular → tabular}/layerwise_cell_density.py +9 -3
  32. siibra/features/{molecular → tabular}/receptor_density_fingerprint.py +3 -2
  33. siibra/features/{molecular → tabular}/receptor_density_profile.py +6 -2
  34. siibra/features/tabular/regional_timeseries_activity.py +213 -0
  35. siibra/features/{basetypes → tabular}/tabular.py +14 -9
  36. siibra/livequeries/allen.py +1 -1
  37. siibra/livequeries/bigbrain.py +2 -3
  38. siibra/livequeries/ebrains.py +3 -9
  39. siibra/livequeries/query.py +1 -1
  40. siibra/locations/location.py +4 -3
  41. siibra/locations/point.py +21 -17
  42. siibra/locations/pointset.py +2 -2
  43. siibra/retrieval/__init__.py +1 -1
  44. siibra/retrieval/cache.py +8 -2
  45. siibra/retrieval/datasets.py +149 -29
  46. siibra/retrieval/repositories.py +19 -8
  47. siibra/retrieval/requests.py +98 -116
  48. siibra/volumes/gifti.py +26 -11
  49. siibra/volumes/neuroglancer.py +35 -19
  50. siibra/volumes/nifti.py +8 -9
  51. siibra/volumes/parcellationmap.py +341 -184
  52. siibra/volumes/sparsemap.py +67 -53
  53. siibra/volumes/volume.py +25 -13
  54. {siibra-0.4a33.dist-info → siibra-0.4a46.dist-info}/METADATA +4 -3
  55. siibra-0.4a46.dist-info/RECORD +69 -0
  56. {siibra-0.4a33.dist-info → siibra-0.4a46.dist-info}/WHEEL +1 -1
  57. siibra/features/basetypes/feature.py +0 -248
  58. siibra/features/fibres/__init__.py +0 -14
  59. siibra/features/functional/__init__.py +0 -14
  60. siibra/features/molecular/__init__.py +0 -26
  61. siibra/samplers/bigbrain.py +0 -181
  62. siibra-0.4a33.dist-info/RECORD +0 -71
  63. {siibra-0.4a33.dist-info → siibra-0.4a46.dist-info}/LICENSE +0 -0
  64. {siibra-0.4a33.dist-info → siibra-0.4a46.dist-info}/top_level.txt +0 -0
siibra/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4a33
1
+ 0.4a46
siibra/__init__.py CHANGED
@@ -119,6 +119,7 @@ def __dir__():
119
119
  "use_configuration",
120
120
  "extend_configuration",
121
121
  "get_region",
122
+ "find_regions",
122
123
  "get_map",
123
124
  "get_template",
124
125
  "MapType",
@@ -130,4 +131,5 @@ def __dir__():
130
131
  "set_ebrains_token",
131
132
  "vocabularies",
132
133
  "__version__",
134
+ "cache",
133
135
  ]
siibra/commons.py CHANGED
@@ -18,9 +18,11 @@ import re
18
18
  from enum import Enum
19
19
  from nibabel import Nifti1Image
20
20
  import logging
21
+ from tqdm import tqdm
21
22
  import numpy as np
22
23
  from typing import Generic, Iterable, Iterator, List, TypeVar, Union, Dict
23
24
  from skimage.filters import gaussian
25
+ from dataclasses import dataclass
24
26
 
25
27
  logger = logging.getLogger(__name__.split(os.path.extsep)[0])
26
28
  ch = logging.StreamHandler()
@@ -36,9 +38,20 @@ KEYCLOAK_CLIENT_SECRET = os.getenv("KEYCLOAK_CLIENT_SECRET")
36
38
  SIIBRA_CACHEDIR = os.getenv("SIIBRA_CACHEDIR")
37
39
  SIIBRA_LOG_LEVEL = os.getenv("SIIBRA_LOG_LEVEL", "INFO")
38
40
  SIIBRA_USE_CONFIGURATION = os.getenv("SIIBRA_USE_CONFIGURATION")
41
+
42
+ SIIBRA_USE_LOCAL_SNAPSPOT = os.getenv("SIIBRA_USE_LOCAL_SNAPSPOT")
43
+
39
44
  with open(os.path.join(ROOT_DIR, "VERSION"), "r") as fp:
40
45
  __version__ = fp.read().strip()
41
46
 
47
+ @dataclass
48
+ class CompareMapsResult:
49
+ intersection_over_union: float
50
+ intersection_over_first: float
51
+ intersection_over_second: float
52
+ correlation: float
53
+ weighted_mean_of_first: float
54
+ weighted_mean_of_second: float
42
55
 
43
56
  T = TypeVar("T")
44
57
 
@@ -233,6 +246,15 @@ QUIET = LoggingContext("ERROR")
233
246
  VERBOSE = LoggingContext("DEBUG")
234
247
 
235
248
 
249
+ def siibra_tqdm(iterable: Iterable[T]=None, *args, **kwargs):
250
+ return tqdm(
251
+ iterable,
252
+ *args,
253
+ disable=kwargs.pop("disable", False) or (logger.level > 20),
254
+ **kwargs
255
+ )
256
+
257
+
236
258
  def create_key(name: str):
237
259
  """
238
260
  Creates an uppercase identifier string that includes only alphanumeric
@@ -350,6 +372,21 @@ def affine_scaling(affine):
350
372
  return np.prod(unit_lengths)
351
373
 
352
374
 
375
+ def iterate_connected_components(img: Nifti1Image):
376
+ """
377
+ Provide an iterator over masks of connected components in the given image.
378
+ """
379
+ from skimage import measure
380
+ imgdata = np.asanyarray(img.dataobj).squeeze()
381
+ components = measure.label(imgdata > 0)
382
+ component_labels = np.unique(components)
383
+ assert component_labels[0] == 0
384
+ return (
385
+ (label, Nifti1Image((components == label).astype('uint8'), img.affine))
386
+ for label in component_labels[1:]
387
+ )
388
+
389
+
353
390
  def compare_maps(map1: Nifti1Image, map2: Nifti1Image):
354
391
  """
355
392
  Compare two maps, given as Nifti1Image objects.
@@ -396,7 +433,14 @@ def compare_maps(map1: Nifti1Image, map2: Nifti1Image):
396
433
  m1, m2 = ((_ > 0).astype("uint8") for _ in [v1, v2])
397
434
  intersection = np.minimum(m1, m2).sum()
398
435
  if intersection == 0:
399
- return {"IoU": 0, "contained": 0, "contains": 0, "correlation": 0}
436
+ return CompareMapsResult(
437
+ intersection_over_union=0,
438
+ intersection_over_first=0,
439
+ intersection_over_second=0,
440
+ correlation=0,
441
+ weighted_mean_of_first=0,
442
+ weighted_mean_of_second=0,
443
+ )
400
444
 
401
445
  # Compute the nonzero voxels in map1 with their correspondences in map2
402
446
  XYZnz1 = nonzero_coordinates(a1)
@@ -430,13 +474,14 @@ def compare_maps(map1: Nifti1Image, map2: Nifti1Image):
430
474
 
431
475
  bx = (x > 0).astype("uint8")
432
476
  by = (y > 0).astype("uint8")
433
-
434
- return {
435
- "IoU": intersection / np.maximum(bx, by).sum(),
436
- "contained": intersection / N1,
437
- "contains": intersection / N2,
438
- "correlation": r,
439
- }
477
+ return CompareMapsResult(
478
+ intersection_over_union=intersection / np.maximum(bx, by).sum(),
479
+ intersection_over_first=intersection / N1,
480
+ intersection_over_second=intersection / N2,
481
+ correlation=r,
482
+ weighted_mean_of_first=np.sum(x * y) / np.sum(y),
483
+ weighted_mean_of_second=np.sum(x * y) / np.sum(x),
484
+ )
440
485
 
441
486
 
442
487
  class PolyLine:
@@ -13,7 +13,7 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- from ..commons import logger, __version__, SIIBRA_USE_CONFIGURATION
16
+ from ..commons import logger, __version__, SIIBRA_USE_CONFIGURATION, siibra_tqdm
17
17
  from ..retrieval.repositories import GitlabConnector, RepositoryConnector
18
18
  from ..retrieval.exceptions import NoSiibraConfigMirrorsAvailableException
19
19
  from ..retrieval.requests import SiibraHttpRequestError
@@ -21,7 +21,6 @@ from ..retrieval.requests import SiibraHttpRequestError
21
21
  from typing import Union
22
22
  from collections import defaultdict
23
23
  from requests.exceptions import ConnectionError
24
- from tqdm import tqdm
25
24
  from os import path
26
25
 
27
26
 
@@ -47,12 +46,7 @@ class Configuration:
47
46
 
48
47
  CONFIGURATION_EXTENSIONS = []
49
48
 
50
- # lists of loaders for json specification files
51
- # found in the siibra configuration, stored per
52
- # preconfigured class name. These files can
53
- # loaded and fed to the Factory.from_json
54
- # to produce the corresponding object.
55
- spec_loaders = defaultdict(list)
49
+
56
50
 
57
51
  _cleanup_funcs = []
58
52
 
@@ -65,6 +59,13 @@ class Configuration:
65
59
 
66
60
  def __init__(self):
67
61
 
62
+ # lists of loaders for json specification files
63
+ # found in the siibra configuration, stored per
64
+ # preconfigured class name. These files can
65
+ # loaded and fed to the Factory.from_json
66
+ # to produce the corresponding object.
67
+ self.spec_loaders = defaultdict(list)
68
+
68
69
  # retrieve json spec loaders from the default configuration
69
70
  for connector in self.CONFIGURATIONS:
70
71
  try:
@@ -122,11 +123,14 @@ class Configuration:
122
123
  conn = RepositoryConnector._from_url(conn)
123
124
  if not isinstance(conn, RepositoryConnector):
124
125
  raise RuntimeError("conn needs to be an instance of RepositoryConnector or a valid str")
125
- logger.info(f"Extending configuration with {str(conn)}")
126
- cls.CONFIGURATION_EXTENSIONS.append(conn)
127
- # call registered cleanup functions
128
- for func in cls._cleanup_funcs:
129
- func()
126
+ if conn in cls.CONFIGURATION_EXTENSIONS:
127
+ logger.warn(f"The configuration {str(conn)} is already registered.")
128
+ else:
129
+ logger.info(f"Extending configuration with {str(conn)}")
130
+ cls.CONFIGURATION_EXTENSIONS.append(conn)
131
+ # call registered cleanup functions
132
+ for func in cls._cleanup_funcs:
133
+ func()
130
134
 
131
135
  @classmethod
132
136
  def register_cleanup(cls, func):
@@ -143,7 +147,7 @@ class Configuration:
143
147
  result = []
144
148
 
145
149
  if folder not in self.folders:
146
- logger.warn(f"No configuration found for building from configuration folder {folder}.")
150
+ logger.warning(f"No configuration found for building from configuration folder {folder}.")
147
151
  return result
148
152
 
149
153
  from .factory import Factory
@@ -158,12 +162,12 @@ class Configuration:
158
162
  )
159
163
  )
160
164
 
161
- for fname, loader in tqdm(
165
+ for fname, loader in siibra_tqdm(
162
166
  specloaders,
163
167
  total=len(specloaders),
164
168
  desc=f"Loading preconfigured {obj0.__class__.__name__} instances"
165
169
  ):
166
- # filename is added to allow Factory creating reasonable default object identifiers
170
+ # filename is added to allow Factory creating reasonable default object identifiers\
167
171
  obj = Factory.from_json(dict(loader.data, **{'filename': fname}))
168
172
  result.append(obj)
169
173
 
@@ -174,5 +178,5 @@ class Configuration:
174
178
 
175
179
 
176
180
  if SIIBRA_USE_CONFIGURATION:
177
- logger.warn(f"config.SIIBRA_USE_CONFIGURATION defined, use configuration at {SIIBRA_USE_CONFIGURATION}")
181
+ logger.warning(f"config.SIIBRA_USE_CONFIGURATION defined, use configuration at {SIIBRA_USE_CONFIGURATION}")
178
182
  Configuration.use_configuration(SIIBRA_USE_CONFIGURATION)
@@ -14,15 +14,19 @@
14
14
  # limitations under the License.
15
15
 
16
16
  from ..commons import logger, Species
17
- from ..features import anchor
18
- from ..features.molecular import receptor_density_fingerprint, receptor_density_profile
19
- from ..features.cellular import cell_density_profile, layerwise_cell_density
20
- from ..features.basetypes import volume_of_interest
17
+ from ..features import anchor, connectivity
18
+ from ..features.tabular import (
19
+ receptor_density_profile,
20
+ receptor_density_fingerprint,
21
+ cell_density_profile,
22
+ layerwise_cell_density,
23
+ regional_timeseries_activity
24
+ )
25
+ from ..features.image import sections, volume_of_interest
21
26
  from ..core import atlas, parcellation, space, region
22
27
  from ..locations import point, pointset
23
28
  from ..retrieval import datasets, repositories
24
29
  from ..volumes import gifti, volume, nifti, neuroglancer, sparsemap, parcellationmap
25
- from ..features import connectivity
26
30
 
27
31
  from os import path
28
32
  import json
@@ -49,7 +53,9 @@ BUILDFUNCS = {
49
53
  "siibra/feature/fingerprint/receptor/v0.1": "build_receptor_density_fingerprint",
50
54
  "siibra/feature/fingerprint/celldensity/v0.1": "build_cell_density_fingerprint",
51
55
  "siibra/feature/connectivitymatrix/v0.2": "build_connectivity_matrix",
56
+ "siibra/feature/section/v0.1": "build_section",
52
57
  "siibra/feature/voi/v0.1": "build_volume_of_interest",
58
+ "siibra/feature/timeseries/activity/v0.1": "build_activity_timeseries"
53
59
  }
54
60
 
55
61
 
@@ -64,6 +70,10 @@ class Factory:
64
70
  result.append(
65
71
  datasets.EbrainsDataset(id=spec["ebrains"]["minds/core/dataset/v1.0.0"])
66
72
  )
73
+ if "openminds/DatasetVersion" in spec.get("ebrains", {}):
74
+ result.append(
75
+ datasets.EbrainsV3DatasetVersion(id=spec["ebrains"]["openminds/DatasetVersion"])
76
+ )
67
77
  return result
68
78
 
69
79
  @classmethod
@@ -72,7 +82,7 @@ class Factory:
72
82
  for vspec in volume_specs:
73
83
  if space_id:
74
84
  if 'space' in vspec:
75
- logger.warn(f"Replacing space spec {vspec['space']} in volume spec with {space_id}")
85
+ logger.warning(f"Replacing space spec {vspec['space']} in volume spec with {space_id}")
76
86
  vspec['space'] = {"@id": space_id}
77
87
  if name and vspec.get('name') is None: # only use provided name if the volume has no specific name
78
88
  vspec['name'] = name
@@ -135,7 +145,7 @@ class Factory:
135
145
  reftag=repospec['branch']
136
146
  )
137
147
  else:
138
- logger.warn(
148
+ logger.warning(
139
149
  "Do not know how to create a repository "
140
150
  f"connector from specification type {spectype}."
141
151
  )
@@ -236,7 +246,7 @@ class Factory:
236
246
  break
237
247
  else:
238
248
  if srctype not in cls._warnings_issued:
239
- logger.warn(f"No provider defined for volume Source type {srctype}")
249
+ logger.warning(f"No provider defined for volume Source type {srctype}")
240
250
  cls._warnings_issued.append(srctype)
241
251
 
242
252
  assert all([isinstance(provider, volume.VolumeProvider) for provider in providers])
@@ -245,6 +255,7 @@ class Factory:
245
255
  providers=providers,
246
256
  name=spec.get("name", {}),
247
257
  variant=spec.get("variant"),
258
+ datasets=cls.extract_datasets(spec),
248
259
  )
249
260
 
250
261
  return result
@@ -267,7 +278,7 @@ class Factory:
267
278
  else:
268
279
  max_z = max(
269
280
  d.get('z', 0)
270
- for _, l in spec.get("indices", {}).items()
281
+ for l in spec.get("indices", {}).values()
271
282
  for d in l
272
283
  ) + 1
273
284
  if max_z > MIN_VOLUMES_FOR_SPARSE_MAP:
@@ -364,17 +375,61 @@ class Factory:
364
375
  datasets=cls.extract_datasets(spec),
365
376
  )
366
377
 
378
+ @classmethod
379
+ def build_section(cls, spec):
380
+ vol = cls.build_volume(spec)
381
+ kwargs = {
382
+ "name": spec.get('name', ""),
383
+ "region": spec.get('region', None),
384
+ "space_spec": vol._space_spec,
385
+ "providers": vol._providers.values(),
386
+ "datasets": cls.extract_datasets(spec),
387
+ }
388
+ modality = spec.get('modality', "")
389
+ if modality == "cell body staining":
390
+ return sections.CellbodyStainedSection(**kwargs)
391
+ else:
392
+ raise ValueError(f"No method for building image section feature type {modality}.")
393
+
367
394
  @classmethod
368
395
  def build_volume_of_interest(cls, spec):
369
396
  vol = cls.build_volume(spec)
370
- return volume_of_interest.VolumeOfInterest(
371
- name=vol.name,
372
- modality=spec.get('modality', ""),
373
- region=spec.get('region', None),
374
- space_spec=vol._space_spec,
375
- providers=vol._providers.values(),
376
- datasets=cls.extract_datasets(spec),
377
- )
397
+ kwargs = {
398
+ "name": spec.get('name', ""),
399
+ "region": spec.get('region', None),
400
+ "space_spec": vol._space_spec,
401
+ "providers": vol._providers.values(),
402
+ "datasets": cls.extract_datasets(spec),
403
+ }
404
+ modality = spec.get('modality', "")
405
+ if modality == "cell body staining":
406
+ return volume_of_interest.CellBodyStainedVolumeOfInterest(**kwargs)
407
+ elif modality == "blockface":
408
+ return volume_of_interest.BlockfaceVolumeOfInterest(**kwargs)
409
+ elif modality == "PLI HSV fibre orientation map":
410
+ return volume_of_interest.PLIVolumeOfInterest(
411
+ modality="HSV fibre orientation map", **kwargs
412
+ )
413
+ elif modality == "transmittance":
414
+ return volume_of_interest.PLIVolumeOfInterest(
415
+ modality="transmittance", **kwargs
416
+ )
417
+ elif modality == "XPCT":
418
+ return volume_of_interest.XPCTVolumeOfInterest(
419
+ modality="XPCT", **kwargs
420
+ )
421
+ # elif modality == "segmentation":
422
+ # return volume_of_interest.SegmentedVolumeOfInterest(**kwargs)
423
+ elif modality == "T2 weighted MRI":
424
+ return volume_of_interest.MRIVolumeOfInterest(
425
+ modality="T2", **kwargs
426
+ )
427
+ elif modality == "T1 weighted MRI":
428
+ return volume_of_interest.MRIVolumeOfInterest(
429
+ modality="T1", **kwargs
430
+ )
431
+ else:
432
+ raise ValueError(f"No method for building image section feature type {modality}.")
378
433
 
379
434
  @classmethod
380
435
  def build_connectivity_matrix(cls, spec):
@@ -398,10 +453,31 @@ class Factory:
398
453
  kwargs["paradigm"] = spec.get("paradigm")
399
454
  return connectivity.FunctionalConnectivity(**kwargs)
400
455
  elif modality == "RestingState":
401
- kwargs["paradigm"] = "RestingState"
456
+ kwargs["paradigm"] = spec.get("paradigm", "RestingState")
402
457
  return connectivity.FunctionalConnectivity(**kwargs)
403
458
  else:
404
- raise ValueError(f"Do not know how to build connectivity matrix of type {modality}.")
459
+ raise ValueError(f"No method for building connectivity matrix of type {modality}.")
460
+
461
+ @classmethod
462
+ def build_activity_timeseries(cls, spec):
463
+ modality = spec["modality"]
464
+ kwargs = {
465
+ "cohort": spec["cohort"],
466
+ "modality": modality,
467
+ "regions": spec["regions"],
468
+ "connector": cls.extract_connector(spec),
469
+ "decode_func": cls.extract_decoder(spec),
470
+ "files": spec.get("files", {}),
471
+ "anchor": cls.extract_anchor(spec),
472
+ "description": spec.get("description", ""),
473
+ "datasets": cls.extract_datasets(spec),
474
+ "timestep": spec.get("timestep", ("1 no_unit"))
475
+ }
476
+ if modality == "Regional BOLD signal":
477
+ kwargs["paradigm"] = spec.get("paradigm", "")
478
+ return regional_timeseries_activity.RegionalBOLD(**kwargs)
479
+ else:
480
+ raise ValueError(f"No method for building signal table of type {modality}.")
405
481
 
406
482
  @classmethod
407
483
  def from_json(cls, spec: dict):
siibra/core/atlas.py CHANGED
@@ -184,17 +184,20 @@ class Atlas(concept.AtlasConcept, configuration_folder="atlases"):
184
184
 
185
185
  Parameters
186
186
  ----------
187
- regionspec : any of
188
- - a string with a possibly inexact name, which is matched both
189
- against the name and the identifier key,
190
- - an integer, which is interpreted as a labelindex
191
- - a region object
187
+ regionspec: str, regex, int, Region, MapIndex
188
+ - a string with a possibly inexact name (matched both against the name and the identifier key)
189
+ - a string in '/pattern/flags' format to use regex search (acceptable flags: aiLmsux)
190
+ - a regex applied to region names
191
+ - a Region object
192
192
  all_versions : Bool, default: False
193
193
  If True, matched regions for all versions of a parcellation are returned.
194
+ filter_children : bool, default: True
195
+ If False, children of matched parents will be returned.
194
196
 
195
- Yield
196
- -----
197
- list of matching regions
197
+ Returns
198
+ -------
199
+ list[Region]
200
+ list of regions matching to the regionspec
198
201
  """
199
202
  result = []
200
203
  for p in self._parcellation_ids:
siibra/core/concept.py CHANGED
@@ -85,9 +85,29 @@ class AtlasConcept:
85
85
  else Species.decode(species) # overwritable property implementation below
86
86
  self.shortname = shortname
87
87
  self.modality = modality
88
- self.description = description
89
- self.publications = publications
88
+ self._description = description
89
+ self._publications = publications
90
90
  self.datasets = datasets
91
+
92
+ @property
93
+ def description(self):
94
+ if self._description:
95
+ return self._description
96
+ for ds in self.datasets:
97
+ if ds.description:
98
+ return ds.description
99
+ return ''
100
+
101
+ @property
102
+ def publications(self) -> List[TypePublication]:
103
+ return [
104
+ *self._publications,
105
+ *[{
106
+ 'citation': 'DOI',
107
+ 'url': url.get("url")
108
+ } for ds in self.datasets
109
+ for url in ds.urls]
110
+ ]
91
111
 
92
112
  @property
93
113
  def species(self) -> Species:
@@ -135,12 +155,18 @@ class AtlasConcept:
135
155
  @classmethod
136
156
  def get_instance(cls, spec: str):
137
157
  """
138
- Returns an instance of this class matching the given specification
139
- from its registry, if possible, otherwise None.
140
-
141
- Raises
158
+ Parameters
159
+ ----------
160
+ spec: str
161
+ Specification of the class the instance is requested.
162
+ Returns
142
163
  -------
143
- IndexError: if spec cannot match any instance
164
+ an instance of this class matching the given specification from its
165
+ registry if possible, otherwise None.
166
+ Raises
167
+ ------
168
+ IndexError
169
+ If spec cannot match any instance
144
170
  """
145
171
  if cls.registry() is not None:
146
172
  return cls.registry().get(spec)
@@ -168,7 +194,14 @@ class AtlasConcept:
168
194
 
169
195
  def matches(self, spec):
170
196
  """
171
- Test if the given specification matches the name, key or id of the concept.
197
+ Parameters
198
+ ----------
199
+ spec: str
200
+ Specification checked within the concept name, key or id
201
+ Returns
202
+ -------
203
+ bool
204
+ Whether the given specification matches the name, key or id of the concept.
172
205
  """
173
206
  if isinstance(spec, self.__class__) and (spec == self):
174
207
  return True