Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions bluecellulab/cell/morphio_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from numpy.linalg import eig, norm

from bluecellulab.exceptions import BluecellulabError
from pathlib import Path


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down
37 changes: 37 additions & 0 deletions tests/test_cell/test_morphology_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down