clabtoolkit 0.4.1__tar.gz → 0.4.3__tar.gz

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.
Files changed (88) hide show
  1. clabtoolkit-0.4.3/HISTORY.rst +63 -0
  2. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/PKG-INFO +10 -1
  3. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/README.rst +5 -0
  4. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/__init__.py +1 -1
  5. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/bidstools.py +105 -18
  6. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/colorstools.py +21 -7
  7. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/config/config.json +1 -0
  8. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/connectivitytools.py +489 -194
  9. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/dicomtools.py +1 -1
  10. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/dwitools.py +297 -16
  11. clabtoolkit-0.4.3/clabtoolkit/dwitools_utils.py +107 -0
  12. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/freesurfertools.py +1038 -441
  13. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/imagetools.py +327 -81
  14. clabtoolkit-0.4.3/clabtoolkit/imagetools_utils.py +81 -0
  15. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/misctools.py +71 -2
  16. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/morphometrytools.py +317 -35
  17. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/networktools.py +78 -214
  18. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/parcellationtools.py +981 -17
  19. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/plottools.py +83 -0
  20. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/pointstools.py +9 -10
  21. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/surfacetools.py +5 -7
  22. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/tracttools.py +21 -0
  23. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/visualization_utils.py +11 -0
  24. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/visualizationtools.py +535 -1
  25. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit.egg-info/PKG-INFO +10 -1
  26. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit.egg-info/SOURCES.txt +11 -3
  27. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit.egg-info/requires.txt +4 -0
  28. clabtoolkit-0.4.3/docs/source/_static/clabtoolkit_ecosystem.png +0 -0
  29. clabtoolkit-0.4.3/docs/source/_static/clabtoolkit_favicon.png +0 -0
  30. clabtoolkit-0.4.3/docs/source/_static/clabtoolkit_logo.png +0 -0
  31. clabtoolkit-0.4.3/docs/source/_static/clabtoolkit_logo_notext.png +0 -0
  32. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/conf.py +3 -0
  33. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/dicomtools.rst +12 -38
  34. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/pyproject.toml +5 -1
  35. clabtoolkit-0.4.3/usage_examples/VisClasses4Paper.ipynb +1011 -0
  36. clabtoolkit-0.4.3/usage_examples/clabtoolkit-OHBM2026-final.ipynb +2069 -0
  37. clabtoolkit-0.4.3/usage_examples/clabtoolkit_examples.ipynb +1358 -0
  38. clabtoolkit-0.4.3/usage_examples/examples-bidstools-methods.ipynb +1366 -0
  39. clabtoolkit-0.4.3/usage_examples/examples-freesurfertools-methods.ipynb +2595 -0
  40. clabtoolkit-0.4.3/usage_examples/examples-parcellationtools-methods.ipynb +2351 -0
  41. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/usage_examples/examples-surfacetools-methods.ipynb +44 -6
  42. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/usage_examples/examples-visualizationtools-methods.ipynb +3 -5
  43. clabtoolkit-0.4.1/HISTORY.rst +0 -35
  44. clabtoolkit-0.4.1/docs/build/html/_static/file.png +0 -0
  45. clabtoolkit-0.4.1/docs/build/html/_static/minus.png +0 -0
  46. clabtoolkit-0.4.1/docs/build/html/_static/plus.png +0 -0
  47. clabtoolkit-0.4.1/usage_examples/examples-parcellationtools-methods.ipynb +0 -1808
  48. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/AUTHORS.rst +0 -0
  49. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/CONTRIBUTING.rst +0 -0
  50. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/LICENSE +0 -0
  51. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/MANIFEST.in +0 -0
  52. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/build_visualization_layout.py +0 -0
  53. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/config/bids.json +0 -0
  54. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/config/lobes.json +0 -0
  55. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/config/stats_mapping.json +0 -0
  56. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/config/viz_views.json +0 -0
  57. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/config/viz_views_test.json +0 -0
  58. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/connectome.py +0 -0
  59. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/misctools_utils.py +0 -0
  60. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/old_dwitool.py +0 -0
  61. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/pipelinetools.py +0 -0
  62. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/qcqatools.py +0 -0
  63. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit/segmentationtools.py +0 -0
  64. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit.egg-info/dependency_links.txt +0 -0
  65. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/clabtoolkit.egg-info/top_level.txt +0 -0
  66. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/Makefile +0 -0
  67. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/clabtoolkit.rst +0 -0
  68. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/make.bat +0 -0
  69. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/modules.rst +0 -0
  70. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/index.rst +0 -0
  71. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/bidstools.rst +0 -0
  72. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/connectivitytools.rst +0 -0
  73. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/dwitools.rst +0 -0
  74. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/freesurfertools.rst +0 -0
  75. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/imagetools.rst +0 -0
  76. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/misctools.rst +0 -0
  77. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/morphometrytools.rst +0 -0
  78. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/networktools.rst +0 -0
  79. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/parcellationtools.rst +0 -0
  80. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/pipelinetools.rst +0 -0
  81. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/plottools.rst +0 -0
  82. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/qcqatools.rst +0 -0
  83. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/segmentationtools.rst +0 -0
  84. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/surfacetools.rst +0 -0
  85. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/docs/source/modules/visualizationtools.rst +0 -0
  86. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/setup.cfg +0 -0
  87. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/usage_examples/examples-colorstools-methods.ipynb +0 -0
  88. {clabtoolkit-0.4.1 → clabtoolkit-0.4.3}/usage_examples/examples-misctools-methods.ipynb +0 -0
@@ -0,0 +1,63 @@
1
+ =======
2
+ History
3
+ =======
4
+
5
+ 0.4.3 (2026-06-10)
6
+ ------------------
7
+
8
+ * Added compute_fc_matrix method to Parcellation class
9
+ * Added get_info method to Connectome and Parcellation classes
10
+ * Added create_carpet_plot function in visualizationtools
11
+ * Added compute_scalar_maps_from_tensor in diffusiontools
12
+ * Added RegionTimeSeries object and integration across the package
13
+ * Added method to generate connectomes
14
+ * Added connected components computation from edge arrays
15
+ * Added region lookup methods in AnnotParcellation
16
+ * Added comprehensive get_info() for AnnotParcellation
17
+ * Added method to binarize and dilate 3D arrays by millimeter distance
18
+ * Added support for pathlib.Path objects as inputs
19
+ * Added option to make TSV file compatible with templateflow
20
+ * Added new colors to the bcolors class
21
+ * Added bundle id map and updated names
22
+ * Added notebook with examples for OHBM 2026
23
+ * Added new ecosystem figure and logo
24
+ * Refactored stats_from_vector for edge cases with nonzeros_only=True
25
+ * Refactored AnnotParcellation gii2annot/annot2gii to use nibabel instead of FreeSurfer mris_convert
26
+ * Extended merge_to_4d method to deal with DWI data
27
+ * Fixed strict entity and suffix validation in is_bids_filename
28
+ * Fixed pyvista notebook crash from threaded display
29
+ * Fixed color table names bug
30
+ * Fixed dimensions field bugs ("dims" vs "dim")
31
+ * Multiple bug fixes, dependency updates, and documentation improvements
32
+
33
+ 0.4.1 (2026-02-19)
34
+ ------------------
35
+
36
+ * Major refactoring of Parcellation class with improved attribute handling
37
+ * Enhanced color table loading and export with multiple format support
38
+ * Added region adjacency computation to Parcellation
39
+ * Improved BIDS file entity extraction with parallel processing
40
+ * Added DiffusionScheme class for gradient visualization
41
+ * Enhanced Connectome class initialization and metrics computation
42
+ * Added derivatives inventory functionality
43
+ * Improved surface tools with PyVista fallback support
44
+ * Added usage examples notebooks
45
+ * Multiple bug fixes in mask handling, color processing, and data type conversion
46
+ * Improved documentation and code organization
47
+
48
+ 0.3.4 (2025-01-09)
49
+ ------------------
50
+
51
+ * Enhanced documentation and ReadTheDocs configuration
52
+ * Moved region growing method to imagetools module
53
+ * Improved Sphinx autodoc configuration for better API documentation
54
+ * Multiple improvements to surface visualization and plotting functionality
55
+ * Added new morphometry and connectivity analysis tools
56
+ * Enhanced BIDS dataset handling and entity management
57
+ * Improved surface mesh operations and color mapping
58
+ * Added network analysis tools and visualization capabilities
59
+
60
+ 0.3.1 (2025-05-22)
61
+ ------------------
62
+
63
+ * Fourth release on PyPI.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clabtoolkit
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: A comprehensive toolkit for neuroimaging data processing and analysis
5
5
  Author-email: Yasser Alemán-Gómez <yasseraleman@protonmail.com>
6
6
  Maintainer-email: Yasser Alemán-Gómez <yasseraleman@protonmail.com>
@@ -39,6 +39,10 @@ Requires-Dist: matplotlib>=3.10.3
39
39
  Requires-Dist: pyvista>=0.45.3
40
40
  Requires-Dist: nilearn>=0.12.0
41
41
  Requires-Dist: joblib>=1.5.1
42
+ Requires-Dist: colorama>=0.4.6
43
+ Requires-Dist: dipy>=1.11.0
44
+ Requires-Dist: screeninfo>=0.8.1
45
+ Requires-Dist: ipython>=8.0.0
42
46
  Provides-Extra: dev
43
47
  Requires-Dist: pytest>=8.4.1; extra == "dev"
44
48
  Requires-Dist: pytest-cov>=6.2.1; extra == "dev"
@@ -63,6 +67,11 @@ Dynamic: license-file
63
67
  Connectomics Lab Toolkit
64
68
  ========================
65
69
 
70
+ .. image:: docs/source/_static/clabtoolkit_logo.png
71
+ :alt: clabtoolkit logo
72
+ :align: center
73
+ :width: 400
74
+
66
75
 
67
76
  .. image:: https://img.shields.io/pypi/v/clabtoolkit.svg
68
77
  :target: https://pypi.python.org/pypi/clabtoolkit
@@ -2,6 +2,11 @@
2
2
  Connectomics Lab Toolkit
3
3
  ========================
4
4
 
5
+ .. image:: docs/source/_static/clabtoolkit_logo.png
6
+ :alt: clabtoolkit logo
7
+ :align: center
8
+ :width: 400
9
+
5
10
 
6
11
  .. image:: https://img.shields.io/pypi/v/clabtoolkit.svg
7
12
  :target: https://pypi.python.org/pypi/clabtoolkit
@@ -2,4 +2,4 @@
2
2
 
3
3
  __author__ = """Yasser Alemán-Gómez"""
4
4
  __email__ = "yasseraleman@protonmail.com"
5
- __version__ = "0.4.1"
5
+ __version__ = "0.4.3"
@@ -389,7 +389,18 @@ def insert_entity(
389
389
  # If `entity2add` is empty after filtering, return the original entity
390
390
  if not entity2add:
391
391
  if is_string:
392
+ # Restore `suffix` and `extension` if they were removed
393
+ if suffix:
394
+ entity["suffix"] = suffix
395
+ if extension:
396
+ entity["extension"] = extension
392
397
  return entity2str(entity)
398
+
399
+ # Restore `suffix` and `extension` if they were removed
400
+ if suffix:
401
+ entity["suffix"] = suffix
402
+ if extension:
403
+ entity["extension"] = extension
393
404
  return entity
394
405
 
395
406
  else:
@@ -561,6 +572,7 @@ def recursively_replace_entity_key(root_dir: str, replacements: dict):
561
572
  cltmisc.rename_folders(all_dirs, replacements)
562
573
 
563
574
 
575
+ ####################################################################################################
564
576
  def recursively_delete_entity(root_dir: str, key2rem: Union[List[str], str, dict]):
565
577
  """
566
578
  This method deletes entities in all the files and folders of a BIDs dataset.
@@ -627,6 +639,7 @@ def recursively_delete_entity(root_dir: str, key2rem: Union[List[str], str, dict
627
639
  cltmisc.rename_folders(all_dirs, key2rem_dict)
628
640
 
629
641
 
642
+ ####################################################################################################
630
643
  def recursively_insert_entity(
631
644
  root_dir: str, entity2add: Dict[str, str], prev_entity: str = None
632
645
  ) -> None:
@@ -788,7 +801,6 @@ def get_all_entities(root_dir: str) -> Tuple[Dict[str, Set[str]], List[str]]:
788
801
  suffix = ent_dict.pop("suffix")
789
802
 
790
803
  ent_dict.pop("extension", None)
791
- ent_dict.pop("run", None)
792
804
 
793
805
  return list(ent_dict.keys()), suffix
794
806
 
@@ -1040,7 +1052,12 @@ def entities_to_table(
1040
1052
  entities_dict.pop("extension")
1041
1053
 
1042
1054
  # Extract suffix before removing it
1043
- suffix = entities_dict.pop("suffix", "")
1055
+ if "suffix" in entities_dict:
1056
+ if include_suffix:
1057
+ # Add an entity at the end called "Type"
1058
+ tmp_suffix = entities_dict["suffix"]
1059
+
1060
+ entities_dict.pop("suffix")
1044
1061
 
1045
1062
  if entities_to_extract is not None:
1046
1063
  if isinstance(entities_to_extract, str):
@@ -1079,8 +1096,8 @@ def entities_to_table(
1079
1096
  result_df.insert(0, entity.capitalize(), value)
1080
1097
 
1081
1098
  # Append Type column at the end if requested
1082
- if include_suffix:
1083
- result_df["Type"] = suffix
1099
+ if "tmp_suffix" in locals() and include_suffix:
1100
+ result_df.insert(0, "Type", tmp_suffix)
1084
1101
 
1085
1102
  else:
1086
1103
  if result_df.empty:
@@ -1754,36 +1771,106 @@ def get_derivatives_folders(
1754
1771
 
1755
1772
 
1756
1773
  ####################################################################################################
1757
- def is_bids_filename(filename: str) -> bool:
1774
+ def is_bids_filename(filename: str, extensive: bool = False) -> bool:
1758
1775
  """
1759
1776
  Validates a BIDS filename structure, handling extensions and entity order.
1760
1777
 
1761
- Args:
1762
- filename (str): The filename to validate.
1778
+ Parameters:
1779
+ -----------
1780
+ filename : str
1781
+ The filename to validate, which may include extensions (e.g., .nii.gz).
1782
+
1783
+ extensive : bool, default=False
1784
+ If True, performs additional checks against a BIDS configuration file to ensure that all
1785
+ entities in the filename are defined in the config. If False, only checks the basic structure
1786
+ of the filename.
1787
+
1788
+ Returns:
1789
+ --------
1790
+ bool
1791
+ True if the filename follows BIDS conventions, False otherwise.
1792
+
1793
+ Examples:
1794
+ ---------
1795
+ >>> is_bids_filename("sub-01_ses-pre_task-rest_bold.nii.gz")
1796
+ True
1797
+ >>> is_bids_filename("sub-01_ses-pre_task-rest_bold")
1798
+ True
1799
+ >>> is_bids_filename("sub-01_ses-pre_task-rest.nii.gz")
1800
+ True
1801
+ >>> is_bids_filename("sub-01_ses-pre_task-rest")
1802
+ True
1803
+ >>> is_bids_filename("sub-01_ses-pre_task-rest_bold_extra.nii.gz")
1804
+ False
1805
+ >>> is_bids_filename("sub-01_ses-pre_task-rest_bold_extra")
1763
1806
 
1764
- Returns
1765
- bool: True if valid BIDS filename, False otherwise.
1766
1807
  """
1767
1808
  # Remove extension if present
1768
1809
  filename = cltmisc.get_real_basename(filename)
1769
1810
  base_filename = filename.split(".")[0]
1770
1811
 
1771
1812
  parts = base_filename.split("_")
1772
- if not parts:
1813
+ if len(parts) < 2:
1814
+ # Need at least one entity-label pair + a suffix
1773
1815
  return False
1774
1816
 
1775
1817
  entity_pattern = re.compile(r"^[a-zA-Z0-9]+-[a-zA-Z0-9]+$")
1818
+ suffix_pattern = re.compile(r"^[a-zA-Z0-9]+$")
1776
1819
 
1777
- # Check that at least one entity-label pair is present
1778
- has_entity_label = False
1779
- for part in parts:
1780
- if "-" in part:
1781
- has_entity_label = True
1782
- if not entity_pattern.match(part):
1783
- return False
1784
- if not has_entity_label:
1820
+ # All parts except the last must be valid entity-label pairs (e.g. sub-01, ses-M00)
1821
+ for part in parts[:-1]:
1822
+ if not entity_pattern.match(part):
1823
+ return False
1824
+
1825
+ # The last part is the suffix (e.g. T1w, bold) — no dash allowed
1826
+ if not suffix_pattern.match(parts[-1]):
1785
1827
  return False
1786
1828
 
1829
+ # Check also that all the entities are on the json config file
1830
+ cwd = os.path.dirname(os.path.abspath(__file__))
1831
+ default_config_path = os.path.join(cwd, "config", "bids.json")
1832
+
1833
+ if extensive:
1834
+ try:
1835
+ with open(default_config_path, "r") as f:
1836
+ config_data = json.load(f)
1837
+
1838
+ if (
1839
+ "bids_entities" in config_data
1840
+ and "raw_entities" in config_data["bids_entities"]
1841
+ and "derivatives_entities" in config_data["bids_entities"]
1842
+ ):
1843
+ valid_entities = set(
1844
+ list(config_data["bids_entities"]["raw_entities"].keys())
1845
+ + list(config_data["bids_entities"]["derivatives_entities"].keys())
1846
+ )
1847
+ valid_suffixes = set(
1848
+ config_data["bids_entities"].get("raw_suffix", [])
1849
+ + config_data["bids_entities"].get("derivatives_suffix", [])
1850
+ )
1851
+ else:
1852
+ raise ValueError(
1853
+ "Default config JSON does not have the expected structure."
1854
+ )
1855
+ except FileNotFoundError:
1856
+ raise FileNotFoundError(
1857
+ f"Default configuration file not found at: {default_config_path}"
1858
+ )
1859
+ except json.JSONDecodeError:
1860
+ raise ValueError(
1861
+ f"Error parsing the default configuration file: {default_config_path}"
1862
+ )
1863
+
1864
+ # Validate each entity key against the known BIDS entities
1865
+ for part in parts[:-1]:
1866
+ entity_key = part.split("-")[0]
1867
+ if entity_key not in valid_entities:
1868
+ return False
1869
+
1870
+ # Validate the suffix against the known BIDS suffixes
1871
+ if parts[-1] not in valid_suffixes:
1872
+ return False
1873
+
1787
1874
  return True
1788
1875
 
1789
1876
 
@@ -122,6 +122,9 @@ class bcolors:
122
122
  BOLD = "\033[1m"
123
123
  ITALIC = "\033[3m"
124
124
  UNDERLINE = "\033[4m"
125
+ DIM = "\033[2m"
126
+ NORMAL = "\033[22m"
127
+ RESET_ALL = "\033[0m"
125
128
 
126
129
 
127
130
  ####################################################################################################
@@ -899,27 +902,38 @@ def harmonize_colors(
899
902
 
900
903
  >>> harmonize_colors(colors, output_format='rgb')
901
904
  array([[255, 87, 51],
902
- [255, 87, 51],
903
- [ 51, 87, 255]], dtype=uint8)
905
+ [255, 87, 51],
906
+ [ 51, 87, 255]], dtype=uint8)
904
907
 
905
908
  >>> harmonize_colors(colors, output_format='rgbnorm')
906
909
  array([[1. , 0.34117647, 0.2 ],
907
- [1. , 0.34117647, 0.2 ],
908
- [0.2 , 0.34117647, 1. ]])
910
+ [1. , 0.34117647, 0.2 ],
911
+ [0.2 , 0.34117647, 1. ]])
909
912
 
910
913
  >>> # Single color input
911
914
  >>> harmonize_colors((255, 87, 51))
912
915
  ['#ff5733']
913
916
  """
914
917
 
915
- # Handle single color inputs
918
+ # Normalize input into iterable of colors
916
919
  if isinstance(colors, str):
917
920
  colors = [colors]
921
+
918
922
  elif isinstance(colors, tuple):
919
923
  colors = [colors]
920
924
 
921
- # Validate input type
922
- if not isinstance(colors, (list, np.ndarray)):
925
+ elif isinstance(colors, np.ndarray):
926
+ if colors.ndim == 1:
927
+ # Single color (e.g., [R,G,B] or [R,G,B,A])
928
+ colors = [colors]
929
+ elif colors.ndim == 2:
930
+ # Multiple colors (Nx3 or Nx4)
931
+ colors = [row for row in colors]
932
+ else:
933
+ raise ValueError("NumPy array must be 1D or 2D for colors")
934
+
935
+ # Final validation
936
+ if not isinstance(colors, list):
923
937
  raise TypeError("Input must be a string, tuple, list, or numpy array")
924
938
 
925
939
  # Validate output format
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "metrics_units": {
3
3
  "thickness": "mm",
4
+ "nvertices": "au",
4
5
  "area": "cm2",
5
6
  "volume": "cm3",
6
7
  "curv": "mm-1",