from pathlib import Path
from typing import Any
import numpy as np
import sbxreader
from neuralib.argp import int_tuple_type
from neuralib.imaging.scanbox.core import SBXInfo
from neuralib.imglib.labeller import SequenceLabeller
from neuralib.typing import PathLike
from neuralib.util.utils import uglob
__all__ = ['SBXViewer']
[docs]
class SBXViewer:
"""wrapper for sbxreader
`Dimension parameters`:
F = number of frames
P = number of optical planes
C = number of PMT channels
W = FOV width
H = FOV height
"""
info: SBXInfo
""":class:`~neuralib.imaging.scanbox.core.SBXInfo`"""
sbx_map: sbxreader.sbx_memmap
"""`Array[float, [F, P, C, W, H]]`"""
[docs]
def __init__(self, directory: PathLike):
"""
:param directory: scanbox file (.sbx). In the same directory should contain the corresponding .mat file
"""
if not Path(directory).is_dir():
raise NotADirectoryError(f'{directory}')
sbx = uglob(directory, '*.sbx')
self.sbx_map = sbxreader.sbx_memmap(sbx)
info = uglob(directory, '*.mat')
self.info = SBXInfo.load(info)
@property
def meta(self) -> dict[str, Any]:
return self.sbx_map.metadata
@property
def version(self) -> int:
return int(self.info.scanbox_version)
@property
def height(self) -> int:
return self.info.sz[0]
@property
def width(self) -> int:
return self.info.sz[1]
@property
def n_planes(self) -> int:
if not len(self.info.otwave) and self.info.volscan == 0:
return 1
elif self.info.volscan == 1:
return len(self.info.otwave)
raise RuntimeError('')
@property
def n_channels(self) -> int:
"""sbx issue, a bit of hard-coded"""
if self.info.nchan is None:
if self.info.channels == 2:
return 1
elif self.info.channels == 1:
return 2
else:
raise RuntimeError('')
else:
return self.info.nchan
@property
def n_frames(self) -> int:
"""number of frames/images"""
return int(self.info.config.frames / self.n_planes)
[docs]
def play(self,
frames: slice | np.ndarray | None,
plane: int,
channel: int):
"""
Play the selected frames using customized CV2 player.
See :class:`~neuralib.imglib.labeller.SequenceLabeller`
:param frames: selected frames. If None, play all sequences
:param plane: number of optical planes
:param channel: number of PMT channel
:return:
"""
if frames is None:
frames = np.arange(0, self.n_frames)
data = self.sbx_map[frames, plane, channel, :, :]
data = np.asarray(data)
SequenceLabeller.load_sequences(data).main()
[docs]
def to_tiff(self,
frames: slice | np.ndarray | None,
plane: int,
channel: int,
output: PathLike):
"""
Convert the selected frames to tiff file
:param frames: selected frames. If None, convert all sequences
:param plane: number of optical planes
:param channel: number of PMT channel
:param output: output filename
:return:
"""
import tifffile
if frames is None:
frames = np.arange(0, self.n_frames)
data = self.sbx_map[frames, plane, channel, :, :]
tifffile.imwrite(output, data)
def main():
import argparse
ap = argparse.ArgumentParser(description='view or save the sbx file, If specify the output using -O, save as tiff'
'otherwise, play.')
ap.add_argument('-D', '--dir', type=Path, required=True, help='directory containing .sbx/.mat scanbox output',
dest='directory')
ap.add_argument('-F', '--frames', metavar='SLICE', type=int_tuple_type, default=None,
help='indices of image sequences, if None, find all frames', dest='frames')
ap.add_argument('-P', '--plane', type=int, required=True, help='which optic plane', dest='plane')
ap.add_argument('-C', '--channel', type=int, required=True, help='which pmt channel', dest='channel')
ap.add_argument('-O', '--output', default=None, help='tiff output, if None, display the sequence', dest='output')
opt = ap.parse_args()
viewer = SBXViewer(opt.directory)
frames = np.arange(*opt.frames).astype(int) if opt.frames is not None else None
#
if opt.output is None:
viewer.play(frames, opt.plane, opt.channel)
else:
viewer.to_tiff(frames, opt.plane, opt.channel, opt.output)
if __name__ == '__main__':
main()