-
Notifications
You must be signed in to change notification settings - Fork 0
Adding thumbnail generating function #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,140 @@ | |
|
||
import matplotlib.pyplot as plt | ||
import pathlib | ||
import numpy as np | ||
from typing import Optional, Tuple | ||
from bluecellulab.tools import calculate_rheobase | ||
from bluecellulab.circuit.circuit_access import EmodelProperties | ||
from bluecellulab.cell import Cell | ||
from bluecellulab.simulation.neuron_globals import NeuronGlobals | ||
from bluecellulab import Simulation | ||
|
||
|
||
def generate_cell_thumbnail( | ||
template_path: str, | ||
morphology_path: Optional[str] = None, | ||
threshold_current: Optional[float] = None, | ||
holding_current: Optional[float] = None, | ||
template_format: str = "v6", | ||
save_figure: bool = True, | ||
output_path: str = "cell_thumbnail.png", | ||
show_figure: bool = False, | ||
v_init: Optional[float] = -80.0, | ||
celsius: Optional[float] = 34.0, | ||
) -> Tuple[np.ndarray, np.ndarray]: | ||
"""Generate a thumbnail image of a Cell object's step response. | ||
|
||
Args: | ||
template_path: Path to the hoc file. | ||
morphology_path: Path to the morphology file. Default is None. | ||
threshold_current: Threshold current for the cell. Default is None. | ||
holding_current: Holding current for the cell. Default is None. | ||
template_format: Template format. Default is "v6". | ||
save_figure: Whether to save the figure. Default is True. | ||
output_path: Path where to save the thumbnail image. | ||
show_figure: Whether to display the figure. Default is False. | ||
v_init: Initial membrane potential. Default is -80.0 mV. | ||
celsius: Temperature in Celsius. Default is 34.0. | ||
|
||
Returns: | ||
Tuple containing (time, voltage) arrays of the simulation. | ||
""" | ||
# If the threshold and holding_current is not known, | ||
# we use calculate_rheobase() to find the threshold_current. | ||
# For this case, we set the holding_current to 0.0 nA. | ||
# if threshold_current is None: | ||
threshold_current = 1.0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are you overwriting this value? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The threshold current calculation should be done every time as the morphology of the me-combination will change and it won't be emodel threshold to be sure we are not using the emodel threshold. The value was overwritten just to initialise of the cell. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this can be called after we compute the threshold, and we just get it from MEModelCalibration. So we can remove the threshold computation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. I will not overwrite the threshold and holding currents. I will pass the emodel_properties instead. |
||
if holding_current is None: | ||
holding_current = 0.0 | ||
|
||
emodel_properties = EmodelProperties( | ||
threshold_current=threshold_current, | ||
holding_current=holding_current | ||
) | ||
|
||
# Initialise cell | ||
thumbnail_cell = Cell( | ||
template_path=template_path, | ||
morphology_path=morphology_path, | ||
template_format=template_format, | ||
emodel_properties=emodel_properties, | ||
) | ||
holding_current = 0.0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here also, you are overwriting holding current and computing rheobase even if we have passed those values to the function There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same but for holding current. |
||
# Calculate rheobase | ||
newthreshold_current = calculate_rheobase( | ||
thumbnail_cell, | ||
section="soma[0]", | ||
segx=0.5 | ||
) | ||
print(f"Threshold current: {newthreshold_current}") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use logger.info instead of print |
||
print(f"Holding current: {holding_current}") | ||
#delete thumbnail_cell | ||
thumbnail_cell.delete() | ||
|
||
# Update emodel properties | ||
emodel_properties = EmodelProperties( | ||
threshold_current=newthreshold_current, | ||
holding_current=holding_current | ||
) | ||
|
||
# Create cell | ||
thumbnail_cell = Cell( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you don't need to re-create the cell There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you don't recreate the cell, the thumbnail generation step protocol would persist even after the thumbnail was generated. I also delete the cell at the end. |
||
template_path=template_path, | ||
morphology_path=morphology_path, | ||
template_format=template_format, | ||
emodel_properties=emodel_properties, | ||
) | ||
|
||
# Add 130% threshold current injection | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment is not consistent with code |
||
step_amplitude = newthreshold_current * 1.1 | ||
step_duration = 2000 | ||
start_time = 700 | ||
thumbnail_cell.add_step( | ||
start_time=start_time, | ||
stop_time=start_time+step_duration, | ||
level=step_amplitude | ||
) | ||
|
||
# Set up simulation | ||
sim = thumbnail_cell.simulation if hasattr(thumbnail_cell, 'simulation') else None | ||
if sim is None: | ||
sim = Simulation() | ||
sim.add_cell(thumbnail_cell) | ||
|
||
neuron_globals = NeuronGlobals.get_instance() | ||
# neuron_globals.temperature = celsius | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are those commented out? |
||
# neuron_globals.v_init = v_init | ||
|
||
# Run simulation | ||
tstop = step_duration+300 | ||
sim.run(tstop) | ||
# Get recording data | ||
time = thumbnail_cell.get_time() | ||
voltage = thumbnail_cell.get_soma_voltage() | ||
|
||
# Create figure with default size (matplotlib's default is [6.4, 4.8]) | ||
fig, ax = plt.subplots(figsize=(6.4, 4.8)) | ||
ax.plot(time, voltage, 'k-', linewidth=1.5) | ||
ax.set_xlabel('Time (ms)', fontsize=10) | ||
ax.set_ylabel('Membrane potential (mV)', fontsize=10) | ||
plt.tight_layout() | ||
|
||
if save_figure: | ||
# Create parent directories if they don't exist | ||
output_path = pathlib.Path(output_path) | ||
output_path.parent.mkdir(parents=True, exist_ok=True) | ||
|
||
# Save figure with 300 DPI | ||
plt.savefig(output_path, dpi=300, bbox_inches='tight') | ||
|
||
if show_figure: | ||
plt.show() | ||
else: | ||
plt.close() | ||
|
||
thumbnail_cell.delete() | ||
|
||
return time, voltage | ||
|
||
|
||
def plot_iv_curve( | ||
|
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
"""Tests for analysis plotting functions.""" | ||
import tempfile | ||
from pathlib import Path | ||
from typing import Optional, Tuple | ||
import pytest | ||
import numpy as np | ||
from bluecellulab.analysis.plotting import generate_cell_thumbnail | ||
from bluecellulab.circuit.circuit_access.definition import EmodelProperties | ||
|
||
# Test data paths | ||
TEST_DATA_DIR = (Path(__file__).parent / "examples" | ||
/ "circuit_sonata_quick_scx" / "components") | ||
HOC_FILE = TEST_DATA_DIR / "hoc" / "cADpyr_L2TPC.hoc" | ||
MORPH_FILE = TEST_DATA_DIR / "morphologies" / "asc" / "rr110330_C3_idA.asc" | ||
|
||
# Fixtures | ||
@pytest.fixture(scope="module") | ||
def emodel_properties() -> EmodelProperties: | ||
"""Fixture providing default emodel properties for testing.""" | ||
return EmodelProperties(threshold_current=1.1433533430099487, | ||
holding_current=1.4146618843078613, | ||
AIS_scaler=1.4561502933502197, | ||
soma_scaler=1.0) | ||
|
||
@pytest.fixture | ||
def output_dir(): | ||
"""Fixture providing a temporary directory for test outputs.""" | ||
with tempfile.TemporaryDirectory() as temp_dir: | ||
yield Path(temp_dir) | ||
|
||
class TestGenerateCellThumbnail: | ||
"""Test cases for generate_cell_thumbnail function.""" | ||
|
||
def test_with_explicit_output_path(self, output_dir: Path, emodel_properties: EmodelProperties): | ||
"""Test thumbnail generation with explicit output path.""" | ||
# Skip test if required files don't exist | ||
if not HOC_FILE.exists() or not MORPH_FILE.exists(): | ||
pytest.skip("Required test files not found") | ||
|
||
# Arrange | ||
output_path = output_dir / "cell_thumbnail.png" | ||
|
||
# Act | ||
time, voltage = generate_cell_thumbnail( | ||
template_path=str(HOC_FILE), | ||
morphology_path=str(MORPH_FILE), | ||
template_format="v6", | ||
emodel_properties=emodel_properties, | ||
output_path=output_path | ||
) | ||
|
||
# Assert | ||
assert output_path.exists(), "Thumbnail file was not created" | ||
assert output_path.stat().st_size > 0, "Thumbnail file is empty" | ||
assert len(time) == len(voltage) > 0, "Time and voltage arrays should have matching lengths" | ||
assert isinstance(time, np.ndarray), "Time should be a numpy array" | ||
assert isinstance(voltage, np.ndarray), "Voltage should be a numpy array" | ||
|
||
def test_with_default_output_path(self, tmp_path, emodel_properties: EmodelProperties): | ||
"""Test thumbnail generation with default output path.""" | ||
# Skip test if required files don't exist | ||
if not HOC_FILE.exists() or not MORPH_FILE.exists(): | ||
pytest.skip("Required test files not found") | ||
|
||
# Arrange | ||
default_path = Path("cell_thumbnail.png") | ||
|
||
try: | ||
# Act | ||
time, voltage = generate_cell_thumbnail( | ||
template_path=str(HOC_FILE), | ||
morphology_path=str(MORPH_FILE), | ||
template_format="v6", | ||
emodel_properties=emodel_properties | ||
) | ||
|
||
# Assert | ||
assert default_path.exists(), "Default thumbnail file was not created" | ||
assert default_path.stat().st_size > 0, "Default thumbnail file is empty" | ||
assert len(time) > 0 and len(voltage) > 0, "Output arrays should not be empty" | ||
finally: | ||
# Cleanup | ||
if default_path.exists(): | ||
default_path.unlink() | ||
|
||
@pytest.mark.parametrize("threshold_value", [0, None]) | ||
def test_without_threshold(self, output_dir: Path, emodel_properties: EmodelProperties, threshold_value: Optional[float]): | ||
"""Test thumbnail generation when threshold needs to be calculated.""" | ||
# Skip test if required files don't exist | ||
if not HOC_FILE.exists() or not MORPH_FILE.exists(): | ||
pytest.skip("Required test files not found") | ||
|
||
# Arrange | ||
output_path = output_dir / "cell_no_threshold.png" | ||
|
||
# Create a copy of emodel_properties with no threshold | ||
test_props = EmodelProperties( | ||
threshold_current=0.0 if threshold_value == 0 else 0.1, # Will trigger calculate_rheobase | ||
holding_current=emodel_properties.holding_current, | ||
AIS_scaler=emodel_properties.AIS_scaler, | ||
soma_scaler=emodel_properties.soma_scaler | ||
) | ||
|
||
# Act | ||
time, voltage = generate_cell_thumbnail( | ||
template_path=str(HOC_FILE), | ||
morphology_path=str(MORPH_FILE), | ||
template_format="v6", | ||
emodel_properties=test_props, | ||
output_path=output_path | ||
) | ||
|
||
# Assert | ||
assert output_path.exists(), "Thumbnail file was not created" | ||
assert output_path.stat().st_size > 0, "Thumbnail file is empty" | ||
assert len(time) > 0 and len(voltage) > 0, "Output arrays should not be empty" | ||
|
||
@pytest.mark.parametrize("show_figure", [True, False]) | ||
def test_show_figure_parameter(self, output_dir: Path, emodel_properties: EmodelProperties, show_figure: bool): | ||
"""Test that show_figure parameter works as expected.""" | ||
# Skip test if required files don't exist | ||
if not HOC_FILE.exists() or not MORPH_FILE.exists(): | ||
pytest.skip("Required test files not found") | ||
|
||
# Arrange | ||
output_path = output_dir / f"show_figure_{show_figure}.png" | ||
|
||
# Act | ||
time, voltage = generate_cell_thumbnail( | ||
template_path=str(HOC_FILE), | ||
morphology_path=str(MORPH_FILE), | ||
template_format="v6", | ||
emodel_properties=emodel_properties, | ||
output_path=output_path, | ||
show_figure=show_figure | ||
) | ||
|
||
# Assert | ||
assert output_path.exists(), "Thumbnail file was not created" | ||
assert output_path.stat().st_size > 0, "Thumbnail file is empty" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you reduce the number of function arguments by passing template_params instead?
See https://github.com/openbraininstitute/BlueCelluLab/blob/main/bluecellulab/cell/template.py#L48