diff --git a/CHANGELOG.md b/CHANGELOG.md index 20335e9..48b9186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,11 @@ Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention. -## [0.3.1] - Unreleased +## [0.4.0] - 2022-12-14 + Add - mkdocs documentation + Add - improved docstrings for mkdocs ++ Add - EXTRACT trigger and loader tools ## [0.3.0] - 2022-10-7 @@ -36,6 +37,7 @@ Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and + Add - Readers for: `ScanImage`, `Suite2p`, `CaImAn`. +[0.4.0]: https://github.com/datajoint/element-interface/releases/tag/0.4.0 [0.3.0]: https://github.com/datajoint/element-interface/releases/tag/0.3.0 [0.2.1]: https://github.com/datajoint/element-interface/releases/tag/0.2.1 [0.2.0]: https://github.com/datajoint/element-interface/releases/tag/0.2.0 diff --git a/element_interface/extract_loader.py b/element_interface/extract_loader.py new file mode 100644 index 0000000..ca31949 --- /dev/null +++ b/element_interface/extract_loader.py @@ -0,0 +1,59 @@ +import os +import numpy as np +from pathlib import Path +from datetime import datetime + + +class EXTRACT_loader: + def __init__(self, extract_dir: str): + """Initialize EXTRACT loader class + + Args: + extract_dir (str): string, absolute file path to EXTRACT directory + + Raises: + FileNotFoundError: Could not find EXTRACT results + """ + from scipy.io import loadmat + + try: + extract_file = next(Path(extract_dir).glob("*_extract_output.mat")) + except StopInteration: + raise FileNotFoundError( + f"EXTRACT output result file is not found at {extract_dir}." + ) + + results = loadmat(extract_file) + + self.creation_time = datetime.fromtimestamp(os.stat(extract_file).st_ctime) + self.S = results["output"][0]["spatial_weights"][0] # (Height, Width, MaskId) + self.T = results["output"][0]["temporal_weights"][0] # (Time, MaskId) + + def load_results(self): + """Load the EXTRACT results + + Returns: + masks (dict): Details of the masks identified with the EXTRACT segmentation package. + """ + from scipy.sparse import find + + S_transposed = self.S.transpose([2, 0, 1]) # MaskId, Height, Width + + masks = [] + + for mask_id, s in enumerate(S_transposed): + ypixels, xpixels, weights = find(s) + masks.append( + dict( + mask_id=mask_id, + mask_npix=len(weights), + mask_weights=weights, + mask_center_x=int(np.average(xpixels, weights=weights) + 0.5), + mask_center_y=int(np.average(ypixels, weights=weights) + 0.5), + mask_center_z=None, + mask_xpix=xpixels, + mask_ypix=ypixels, + mask_zpix=None, + ) + ) + return masks diff --git a/element_interface/extract_trigger.py b/element_interface/extract_trigger.py new file mode 100644 index 0000000..7541204 --- /dev/null +++ b/element_interface/extract_trigger.py @@ -0,0 +1,91 @@ +import os +from typing import Union +from pathlib import Path +from textwrap import dedent +from datetime import datetime + + +class EXTRACT_trigger: + m_template = dedent( + """ + % Load Data + data = load('{scanfile}'); + M = data.M; + + % Input Paramaters + config = struct(); + {parameters_list_string} + + % Run EXTRACT + output = extractor(M, config); + save('{output_fullpath}', 'output'); + """ + ) + + def __init__( + self, + scanfile: Union[str, Path], + parameters: dict, + output_dir: Union[str, Path], + ) -> None: + """A helper class to trigger EXTRACT analysis in element-calcium-imaging. + + Args: + scanfile (Union[str, Path]): Full path of the scan + parameters (dict): EXTRACT input paramaters. + output_dir (Union[str, Path]): Directory to store the outputs of EXTRACT analysis. + """ + assert isinstance(parameters, dict) + + self.scanfile = Path(scanfile) + self.output_dir = Path(output_dir) + self.parameters = parameters + + def write_matlab_run_script(self): + """Compose a matlab script and save it with the name run_extract.m. + + The composed script is basically the formatted version of the m_template attribute.""" + + self.output_fullpath = ( + self.output_dir / f"{self.scanfile.stem}_extract_output.mat" + ) + + m_file_content = self.m_template.format( + **dict( + parameters_list_string="\n".join( + [ + f"config.{k} = '{v}';" + if isinstance(v, str) + else f"config.{k} = {str(v).lower()};" + if isinstance(v, bool) + else f"config.{k} = {v};" + for k, v in self.parameters.items() + ] + ), + scanfile=self.scanfile.as_posix(), + output_fullpath=self.output_fullpath.as_posix(), + ) + ).lstrip() + + self.m_file_fp = self.output_dir / "run_extract.m" + + with open(self.m_file_fp, "w") as f: + f.write(m_file_content) + + def run(self): + """Run the matlab run_extract.m script.""" + + self.write_matlab_run_script() + + current_dir = Path.cwd() + os.chdir(self.output_dir) + + try: + import matlab.engine + + eng = matlab.engine.start_matlab() + eng.run_extract() + except Exception as e: + raise e + finally: + os.chdir(current_dir) diff --git a/element_interface/version.py b/element_interface/version.py index 5c90a7e..eb3c802 100644 --- a/element_interface/version.py +++ b/element_interface/version.py @@ -1,3 +1,3 @@ """Package metadata""" -__version__ = "0.3.1" +__version__ = "0.4.0"