Skip to content

Commit 0b7da1d

Browse files
authored
Merge pull request #65 from tdincer/main
Loader and Trigger for EXTRACT
2 parents 8a7cefe + 1583e02 commit 0b7da1d

File tree

4 files changed

+154
-2
lines changed

4 files changed

+154
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention.
44

5-
## [0.3.1] - Unreleased
5+
## [0.4.0] - 2022-12-14
66

77
+ Add - mkdocs documentation
88
+ Add - improved docstrings for mkdocs
9+
+ Add - EXTRACT trigger and loader tools
910

1011
## [0.3.0] - 2022-10-7
1112

@@ -36,6 +37,7 @@ Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and
3637

3738
+ Add - Readers for: `ScanImage`, `Suite2p`, `CaImAn`.
3839

40+
[0.4.0]: https://github.com/datajoint/element-interface/releases/tag/0.4.0
3941
[0.3.0]: https://github.com/datajoint/element-interface/releases/tag/0.3.0
4042
[0.2.1]: https://github.com/datajoint/element-interface/releases/tag/0.2.1
4143
[0.2.0]: https://github.com/datajoint/element-interface/releases/tag/0.2.0
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import os
2+
import numpy as np
3+
from pathlib import Path
4+
from datetime import datetime
5+
6+
7+
class EXTRACT_loader:
8+
def __init__(self, extract_dir: str):
9+
"""Initialize EXTRACT loader class
10+
11+
Args:
12+
extract_dir (str): string, absolute file path to EXTRACT directory
13+
14+
Raises:
15+
FileNotFoundError: Could not find EXTRACT results
16+
"""
17+
from scipy.io import loadmat
18+
19+
try:
20+
extract_file = next(Path(extract_dir).glob("*_extract_output.mat"))
21+
except StopInteration:
22+
raise FileNotFoundError(
23+
f"EXTRACT output result file is not found at {extract_dir}."
24+
)
25+
26+
results = loadmat(extract_file)
27+
28+
self.creation_time = datetime.fromtimestamp(os.stat(extract_file).st_ctime)
29+
self.S = results["output"][0]["spatial_weights"][0] # (Height, Width, MaskId)
30+
self.T = results["output"][0]["temporal_weights"][0] # (Time, MaskId)
31+
32+
def load_results(self):
33+
"""Load the EXTRACT results
34+
35+
Returns:
36+
masks (dict): Details of the masks identified with the EXTRACT segmentation package.
37+
"""
38+
from scipy.sparse import find
39+
40+
S_transposed = self.S.transpose([2, 0, 1]) # MaskId, Height, Width
41+
42+
masks = []
43+
44+
for mask_id, s in enumerate(S_transposed):
45+
ypixels, xpixels, weights = find(s)
46+
masks.append(
47+
dict(
48+
mask_id=mask_id,
49+
mask_npix=len(weights),
50+
mask_weights=weights,
51+
mask_center_x=int(np.average(xpixels, weights=weights) + 0.5),
52+
mask_center_y=int(np.average(ypixels, weights=weights) + 0.5),
53+
mask_center_z=None,
54+
mask_xpix=xpixels,
55+
mask_ypix=ypixels,
56+
mask_zpix=None,
57+
)
58+
)
59+
return masks
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import os
2+
from typing import Union
3+
from pathlib import Path
4+
from textwrap import dedent
5+
from datetime import datetime
6+
7+
8+
class EXTRACT_trigger:
9+
m_template = dedent(
10+
"""
11+
% Load Data
12+
data = load('{scanfile}');
13+
M = data.M;
14+
15+
% Input Paramaters
16+
config = struct();
17+
{parameters_list_string}
18+
19+
% Run EXTRACT
20+
output = extractor(M, config);
21+
save('{output_fullpath}', 'output');
22+
"""
23+
)
24+
25+
def __init__(
26+
self,
27+
scanfile: Union[str, Path],
28+
parameters: dict,
29+
output_dir: Union[str, Path],
30+
) -> None:
31+
"""A helper class to trigger EXTRACT analysis in element-calcium-imaging.
32+
33+
Args:
34+
scanfile (Union[str, Path]): Full path of the scan
35+
parameters (dict): EXTRACT input paramaters.
36+
output_dir (Union[str, Path]): Directory to store the outputs of EXTRACT analysis.
37+
"""
38+
assert isinstance(parameters, dict)
39+
40+
self.scanfile = Path(scanfile)
41+
self.output_dir = Path(output_dir)
42+
self.parameters = parameters
43+
44+
def write_matlab_run_script(self):
45+
"""Compose a matlab script and save it with the name run_extract.m.
46+
47+
The composed script is basically the formatted version of the m_template attribute."""
48+
49+
self.output_fullpath = (
50+
self.output_dir / f"{self.scanfile.stem}_extract_output.mat"
51+
)
52+
53+
m_file_content = self.m_template.format(
54+
**dict(
55+
parameters_list_string="\n".join(
56+
[
57+
f"config.{k} = '{v}';"
58+
if isinstance(v, str)
59+
else f"config.{k} = {str(v).lower()};"
60+
if isinstance(v, bool)
61+
else f"config.{k} = {v};"
62+
for k, v in self.parameters.items()
63+
]
64+
),
65+
scanfile=self.scanfile.as_posix(),
66+
output_fullpath=self.output_fullpath.as_posix(),
67+
)
68+
).lstrip()
69+
70+
self.m_file_fp = self.output_dir / "run_extract.m"
71+
72+
with open(self.m_file_fp, "w") as f:
73+
f.write(m_file_content)
74+
75+
def run(self):
76+
"""Run the matlab run_extract.m script."""
77+
78+
self.write_matlab_run_script()
79+
80+
current_dir = Path.cwd()
81+
os.chdir(self.output_dir)
82+
83+
try:
84+
import matlab.engine
85+
86+
eng = matlab.engine.start_matlab()
87+
eng.run_extract()
88+
except Exception as e:
89+
raise e
90+
finally:
91+
os.chdir(current_dir)

element_interface/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Package metadata"""
22

3-
__version__ = "0.3.1"
3+
__version__ = "0.4.0"

0 commit comments

Comments
 (0)