diff --git a/bluecellulab/cell/morphio_wrapper.py b/bluecellulab/cell/morphio_wrapper.py index fea7f00a..20b53f3b 100644 --- a/bluecellulab/cell/morphio_wrapper.py +++ b/bluecellulab/cell/morphio_wrapper.py @@ -16,6 +16,8 @@ from numpy.linalg import eig, norm from bluecellulab.exceptions import BluecellulabError +from pathlib import Path + logger = logging.getLogger(__name__) @@ -293,6 +295,33 @@ def __str__(self): return f"{self.name}[{self.id}]" +def resolve_case_insensitive_path(path: str) -> str: + p = Path(path) + if p.exists(): + return str(p) + + parent = p.parent + if not parent.exists(): + return path + + for child in parent.iterdir(): + if child.name.lower() == p.name.lower(): + return str(child) + + return path + + +def is_h5_container_path(path: str) -> bool: + candidate = path + while not os.path.exists(candidate): + parent = os.path.dirname(candidate) + if parent == candidate: + return False + candidate = parent + + return os.path.isfile(candidate) and candidate.lower().endswith(".h5") + + class MorphIOWrapper: """Load a MorphIO morphology and generate HOC instantiation commands. @@ -324,7 +353,11 @@ def __init__(self, input_file, options=0): options: Additional ``morphio.Option`` flags OR-ed into ``Option.nrn_order`` when loading. Defaults to ``0``. """ + input_file = str(input_file) + if not is_h5_container_path(input_file): + input_file = resolve_case_insensitive_path(input_file) self._collection_dir, self._morph_name, self._morph_ext = split_morphology_path(input_file) + self._morph_ext = self._morph_ext.lower() self._options = options self._build_morph() # This logic is similar to what's in BaseCell, but at this point we are still diff --git a/tests/test_cell/test_morphology_wrapper.py b/tests/test_cell/test_morphology_wrapper.py index 03e7c17c..9bf4aca0 100644 --- a/tests/test_cell/test_morphology_wrapper.py +++ b/tests/test_cell/test_morphology_wrapper.py @@ -59,6 +59,43 @@ def test_split_morphology_path_container_with_cell_name(self, tmp_path): assert morph_name == "cell_name" assert morph_ext == "" + def test_case_insensitive_morphology_path(self, tmp_path): + p = tmp_path / "cell.ASC" + p.write_text("dummy") + + with patch.object(MorphIOWrapper, "_build_morph"): + with patch.object(MorphIOWrapper, "_get_section_names", return_value=[]): + with patch.object(MorphIOWrapper, "_build_sec_typeid_distrib"): + wrapper = MorphIOWrapper(str(tmp_path / "cell.asc")) + + assert wrapper._morph_name == "cell" + assert wrapper._morph_ext == ".asc" + + def test_case_insensitive_morphology_path_reverse(self, tmp_path): + """Should also work if file is lowercase and input is uppercase.""" + p = tmp_path / "cell.asc" + p.write_text("dummy") + + with patch.object(MorphIOWrapper, "_build_morph"): + with patch.object(MorphIOWrapper, "_get_section_names", return_value=[]): + with patch.object(MorphIOWrapper, "_build_sec_typeid_distrib"): + wrapper = MorphIOWrapper(str(tmp_path / "cell.ASC")) + + assert wrapper._morph_name == "cell" + assert wrapper._morph_ext == ".asc" + + def test_extension_normalization(self, tmp_path): + """Extension should always be normalized to lowercase.""" + p = tmp_path / "cell.SWC" + p.write_text("dummy") + + with patch.object(MorphIOWrapper, "_build_morph"): + with patch.object(MorphIOWrapper, "_get_section_names", return_value=[]): + with patch.object(MorphIOWrapper, "_build_sec_typeid_distrib"): + wrapper = MorphIOWrapper(str(p)) + + assert wrapper._morph_ext == ".swc" + def test_morphology_wrapper_init_success(self): """Test successful MorphologyWrapper initialization with real H5 file.""" # Test with a real H5 file from BlueCelluLab test data