diff --git a/docs/getting_started/installation.md b/docs/getting_started/installation.md index c08e1174..710bf8fe 100644 --- a/docs/getting_started/installation.md +++ b/docs/getting_started/installation.md @@ -31,10 +31,16 @@ this checked-out directory and run: $ conda env create -n cedalion -f environment_dev.yml ``` +Select a descriptive name for the environment. Keep in mind that over time you +may want to have multiple environments in parallel, for example when you are working +on two projects that use different versions of cedalion. A naming scheme that +includes the current date ('cedalion_YYMMDD') is practical but you are free to choose +whatever works best for you. + Afterwards activate the environment and add an editable install of `cedalion` to it: ``` $ conda activate cedalion -$ pip install -e . +$ pip install -e . --no-deps ``` This will also install Jupyter Notebook to run the example notebooks. @@ -93,6 +99,43 @@ $ hatch run build_docs The same procedure as above applies. However, make sure to use a released version from the main branch. +## Updating + +### Updating between releases + +In the past, you cloned the git repository to a local directory using the last released +version on the main branch. During installation, you created a conda environment and +added cedalion from that directory to the environment. + +Updating to a newer version is easiest done by cloning the git repository again to a +different folder and creating a new environment. This way, the installed version remains +usable. It also guarantees that the new environment contains any updated dependencies. + +The following example uses the version suffix in the directory and environment name. + +``` +$ git clone git@github.com:ibs-lab/cedalion.git path/to/cedalion_v25.1.0 +$ cd path/to/cedalion_v25.1.0 +$ conda env create -n cedalion_v25.1.0 -f environment_dev.yml +$ conda activate cedalion_v25.1.0 +$ pip install -e . --no-deps +``` + +Switching between the different cedalion versions is then possible by activating the +corresponding environment. + +### During development + +Cedalion's development happens in the dev branch. The cloned git repository contains the +complete development history and maintains the connection to our main repository at +GitHub. By pulling the recent changes from there or by checking out a commit from the past +the cedalion directory can be brought to any desired version. The conda environment +will then use the checked out version. + +Keep in mind that the cedalion's dependencies changed over time. When pulling recent +changes from dev you might need to update or recreate the environment. + + ## Container Environments diff --git a/pyproject.toml b/pyproject.toml index 78c952d1..877ca954 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ dependencies = [ "pywavefront==1.3.*", "setuptools-scm", "snirf==0.8.*", - "pmcx==0.4.2", + "pmcx>=0.3.3", "pmcxcl==0.3.1", "pyxdf==1.17.0", ] @@ -80,6 +80,11 @@ requires = [ "hatch-conda>=0.5.2", ] +[tool.hatch.build.targets.sdist] +exclude = [ + "/.github", + "/docs", +] [tool.hatch.envs.default] type = "conda" diff --git a/src/cedalion/vis/plot_probe.py b/src/cedalion/vis/plot_probe.py index 56d06a3c..50407db9 100644 --- a/src/cedalion/vis/plot_probe.py +++ b/src/cedalion/vis/plot_probe.py @@ -18,6 +18,7 @@ from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar from matplotlib.backends.qt_compat import QtWidgets from matplotlib.figure import Figure +import matplotlib.pyplot as plt import cedalion import cedalion.typing as cdt @@ -669,3 +670,85 @@ def run_vis( main_gui = _MAIN_GUI(snirfData=blockaverage, geo2d=geo2d, geo3d=geo3d) main_gui.show() sys.exit(app.exec()) + + +def save_plot_probe_image( + blockaverage, + geo2d, + geo3d, + out_file="probe_plot.png", + xscale=1.0, + yscale=1.0, + title=None, + show_optode_labels=True, + show_meas_lines=False, +): + + # Extract probe layout + sPos = geo2d.sel(label=["S" in str(l) for l in geo2d.label.values]) + dPos = geo2d.sel(label=["D" in str(l) for l in geo2d.label.values]) + sourcePos3D = geo3d.sel(label=["S" in str(l) for l in geo3d.label.values]) + detectorPos3D = geo3d.sel(label=["D" in str(l) for l in geo3d.label.values]) + + sPosVal = sPos.values + dPosVal = dPos.values + + src_idx = [np.where(sPos.label == s)[0][0] for s in blockaverage.source.values] + det_idx = [np.where(dPos.label == d)[0][0] for d in blockaverage.detector.values] + + chan_dist = np.linalg.norm(sourcePos3D.values[src_idx] - detectorPos3D.values[det_idx], axis=1) + + # Normalize positions for plotting + all_xy = np.vstack((sPosVal, dPosVal)) + scale = max(all_xy[:, 0].ptp(), all_xy[:, 1].ptp()) + sxy = (sPosVal - all_xy.mean(axis=0)) / scale + dxy = (dPosVal - all_xy.mean(axis=0)) / scale + + sx, sy = sxy[:, 0], sxy[:, 1] + dx, dy = dxy[:, 0], dxy[:, 1] + + # Midpoints of channels + mx = (sx[src_idx] + dx[det_idx]) / 2 + my = (sy[src_idx] + dy[det_idx]) / 2 + + # Time and HRF + t = blockaverage.reltime.values + hrf = blockaverage.values + trial_idx = 0 # First condition only + chrom_colors = [[0.862, 0.078, 0.235], [0, 0, 0.8]] # HbO and HbR + + fig, ax = plt.subplots(figsize=(12, 12)) + + for i_ch in range(len(mx)): + for i_col in range(hrf.shape[2]): + x = mx[i_ch] + xscale * 0.1 * (t - t[0]) / (t[-1] - t[0]) + y = my[i_ch] + yscale * 0.1 * (hrf[trial_idx, i_ch, i_col, :] - hrf.min()) / (hrf.max() - hrf.min()) + ax.plot(x, y, color=chrom_colors[i_col], lw=0.7) + + # Optodes + ax.plot(sx, sy, 'ro', label='Sources', alpha=0.6) + ax.plot(dx, dy, 'bo', label='Detectors', alpha=0.6) + + # # Measurement lines + # for si, di in zip(src_idx, det_idx): + # ax.plot([sx[si], dx[di]], [sy[si], dy[di]], linestyle='--', color='gray', alpha=0.3) + + if show_optode_labels: + for i, label in enumerate(sPos.label.values): + ax.text(sx[i], sy[i], str(label), color="r", fontsize=8, ha="center", va="center") + for i, label in enumerate(dPos.label.values): + ax.text(dx[i], dy[i], str(label), color="b", fontsize=8, ha="center", va="center") + + if show_meas_lines: + for si, di in zip(src_idx, det_idx): + ax.plot([sx[si], dx[di]], [sy[si], dy[di]], linestyle='--', color='gray', alpha=0.3) + + if title: + ax.set_title(title) + + ax.axis('off') + ax.set_aspect('equal') + plt.tight_layout() + plt.savefig(out_file, dpi=300) + plt.close() + print(f"Saved probe plot to: {out_file}")