limnode.py
limnode.py
The limnode.py file location depends on the installation type:
%userprofile%\AppData\Local\Programs\NIS-Express\Python\Lib\site-packages\limnode.pyC:\Program Files\NIS-Express\Python\Lib\site-packages\limnode.pyImports and type definitions
from __future__ import annotations
from dataclasses import dataclass, field, asdict
from types import NoneType
from typing import Any, Callable, cast, Protocol, Sequence, TypeAlias, TypeGuard,
import enum, subprocess, re, os, sys
class ArrayLike(Protocol):
ndim: int
shape: tuple[int, ...]
def __array__(self, dtype=None): ...
CellData: TypeAlias = int|float|str|None
ColData: TypeAlias = list[int|None]|list[float|None]|list[str|None]|ArrayLike
MultiColData: TypeAlias = ColData|list[ColData]|ArrayLike
SafeColSpec: TypeAlias = (
list[str] | list[int]
| re.Pattern
| Callable[[str, dict[str, Any]], bool]
| None
)class LimTableLike(Protocol):
...
class MplFigureLike(Protocol):
def print_png(*args, **kwargs) -> None: ...
def savefig(*args, **kwargs) -> None: ...
def _is_str_list(x: object) -> TypeGuard[list[str]]:
return isinstance(x, list) and all(isinstance(i, str) for i in x)
def _is_int_list(x: object) -> TypeGuard[list[str]]:
return isinstance(x, list) and all(isinstance(i, str) for i in x)
def _is_list_list(x: object) -> TypeGuard[list[list]]:
return isinstance(x, list) and all(isinstance(i, list) for i in x)
def _is_1d_array_list(x: object) -> TypeGuard[list[ArrayLike]]:
return (
isinstance(x, list)
and all((getattr(i, '__array__', False) and i.ndim == 1) for i in x)
)
def _is_1d_array(x: object) -> TypeGuard[ArrayLike]:
return getattr(x, '__array__', False) and cast(ArrayLike, x).ndim == 1
def _is_2d_array(x: object) -> TypeGuard[ArrayLike]:
return getattr(x, '__array__', False) and cast(ArrayLike, x).ndim == 2
class ExperimentLoopType(enum.IntEnum):
loopTypeUnknown = 0
loopTypePlate = 1
loopTypeWell = 2
loopTypeXYSite = 3
loopTypeTLapse = 4
loopTypeZStack = 5
loopTypeCount = 6
loopTypeSlide = 7
loopTypeRegion = 8
loopTypeTemporary= 9
AnyColorDef: TypeAlias = int|str|tuple[int, int, int]Common base types
@dataclass(frozen=True, kw_only=True)
class Node:
name: str
uuid: str
defaultName: str
@dataclass(frozen=True, kw_only=True)
class LoopDef:
name: str
type: ExperimentLoopType
index: int
@dataclass(frozen=True, kw_only=True)
class ParameterDef:
parentNode: Node
parName: str
parUuid: str
parIsInput: bool
loopDefList: list[LoopDef]
def loopNames(
self,
loop_type: ExperimentLoopType|list[ExperimentLoopType]|None = None) -> list[str]:
...
def format_coordinates(
self,
coordinates: tuple[int],
*,
sep: str = '_',
one_based_indexes: bool = True,
leading_zeros: int = 0
) -> str:
...
@dataclass(frozen=True, kw_only=True)
class Metadata:
componentCount: int
bitsPerComponent: int
size: tuple[int, int, int]
calibration: tuple[float, float, float]
alignment: int
isRGB: bool
@property
def width(self) -> int:
return self.size[0]
@property
def height(self) -> int:
return self.size[1]
@property
def depth(self) -> int:
return self.size[2]
@property
def calibrationXY(self) -> float:
return self.calibration[0]
@property
def calibrationZ(self) -> float:
return self.calibration[2]
@property
def calibrated(self) -> tuple[bool, ...]:
return (0 < self.calibration[0], 0 < self.calibration[1], 0 < self.calibration[2])
@property
def units(self) -> tuple[str, ...]:
return tuple("\xB5m" if cal else "px" for cal in self.calibrated)
def withDifferentSize(
self,
size: tuple[int, int, int],
calibration: tuple[float, float, float]|None = None
) -> Metadata:
...
def copyMetadataFrom(self, other: Metadata) -> Metadata:
...
UserParTuple: TypeAlias = tuple[int|float|str, ...]
LoopDefs: TypeAlias = list[LoopDef]The output(...) function
AnyInDef: TypeAlias = InputChannelDef|InputBinaryDef|InputTableDef|None
AnyOutDef: TypeAlias = OutputChannelDef|OutputBinaryDef|OutputTableDef
InDefTuple: TypeAlias = tuple[AnyInDef, ...]
OutDefTuple: TypeAlias = tuple[AnyOutDef, ...]
def output(inp: limnode.InDefTuple,
out: limnode.OutDefTuple,
par: limnode.UserParTuple) -> None:
pass@dataclass(frozen=True, kw_only=True)
class InputChannelDef(ParameterDef, Metadata):
channelName: str
channelColor: int
@dataclass(frozen=True, kw_only=True)
class OutputChannelDef(ParameterDef, Metadata):
assignedParName: str|None = None
channelName: str|None = None
channelColor: int|None = None
def assign(self, param: InputChannelDef) -> OutputChannelDef:
...
def makeNewMono(self, name: str, color: AnyColorDef) -> OutputChannelDef:
...
def makeNewRgb(self, name: str|None = None) -> OutputChannelDef:
...
def makeFloat(self) -> OutputChannelDef:
...
def makeUInt16(self) -> OutputChannelDef:
...
def makeUInt8(self) -> OutputChannelDef:
...
def copyFrom(self, other: OutputChannelDef) -> OutputChannelDef:
...
@dataclass(frozen=True, kw_only=True)
class InputBinaryDef(ParameterDef, Metadata):
binaryName: str
binaryColor: int
@dataclass(frozen=True, kw_only=True)
class OutputBinaryDef(ParameterDef, Metadata):
assignedParName: str|None = None
binaryName: str|None = None
binaryColor: int|None = None
def assign(self, param: InputBinaryDef) -> OutputBinaryDef:
...
def makeNew(self, name: str, color: AnyColorDef) -> OutputBinaryDef:
...
def makeInt32(self) -> OutputBinaryDef:
...
def makeUInt8(self) -> OutputBinaryDef:
...
def copyFrom(self, other: OutputBinaryDef) -> OutputBinaryDef:
...
@dataclass(frozen=True, kw_only=True)
class InputTableDef(ParameterDef):
tableDef: LimTableLike
def accumulatedOver(self) -> list[str]:
return self.tableDef.tableMetadata.get('accumulatedLoops', [])
@dataclass(frozen=True, kw_only=True)
class OutputTableDef(ParameterDef):
assignedParName: str
tableDef: LimTableLike
def assign(self, param: InputTableDef) -> OutputTableDef:
...
# new empty table (optional InputTableDef to copy accumulated loops)
def makeEmpty(self, name: str,
param: InputTableDef|None = None) -> OutputTableDef:
...
# optional InputTableDef to copy columns from or if None add just loopCols
def makeNew(self, name: str,
param: InputTableDef|None = None) -> OutputTableDef:
...
# to make a result - Graph, (optional InputTableDef to copy accumulated loops)
def makeResult(self, name: str,
param: InputTableDef|None = None) -> OutputTableDef:
...
# to add all necessary loop columns
def withLoopCols(self, *,
newColNames: list[str]|None = None) -> OutputTableDef:
...
# to add Entity and ObjectID columns
def withObjectCols(self, *,
newColNames: list[str]|None = None) -> OutputTableDef:
...
# to add Entity and 3D ObjectID columns
def withObject3dCols(self, *,
newColNames: list[str]|None = None) -> OutputTableDef:
...
# and arbitrary data columns based on type
def withIntCol(self,
title: str,
unit: str|None = None,
feature:str|None = None,
id: str|None = None) -> OutputTableDef:
...
def withFloatCol(self,
title: str,
unit: str|None = None,
feature:str|None = None,
id: str|None = None) -> OutputTableDef:
...
def withStringCol(self,
title: str,
unit: str|None = None,
feature:str|None = None,
id: str|None = None) -> OutputTableDef:
...
# set accumulated table manually (prefer referencing input table in makeEmpty)
def setAccumulatedOver(self, *args) -> OutputTableDef:
...
# convenience for the above
def setAccumulatedOverZStack(self) -> OutputTableDef:
...
def setAccumulatedOverTimeLapse(self) -> OutputTableDef:
...
def setAccumulatedOverMultiPoint(self) -> OutputTableDef:
...
def setAccumulatedOverAll(self) -> OutputTableDef:
...
def _withColumn(self,
decltype: str,
title: str,
unit: str|None = None,
feature: str|None = None,
id: str|None = None,
meta: dict[str, Any]|None = None) -> OutputTableDef:
...
def copyFrom(self, other: OutputTableDef) -> OutputTableDef:
...The run(...) function
AnyInData: TypeAlias = InputChannelData|InputBinaryData|InputTableData|None
AnyOutData: TypeAlias = OutputChannelData|OutputBinaryData|OutputTableData
InDataTuple: TypeAlias = tuple[AnyInData, ...]
OutDataTuple: TypeAlias = tuple[AnyOutData, ...]
def run(inp: limnode.InDataTuple,
out: limnode.OutDataTuple,
par: limnode.UserParTuple,
ctx: limnode.RunContext) -> None:
pass@dataclass(frozen=True, kw_only=True)
class ArrayData:
data: ArrayLike|None
# internal method to set an array in shared memory for out-proc
# for setting data use data[:]
def _setData(self, new_data: ArrayLike|None) -> None:
...
@dataclass(frozen=True, kw_only=True)
class RecordedData:
recdata: LimTableLike|None
def _copyRecordedDataFrom(self, other: RecordedData) -> None:
...
@dataclass(frozen=True, kw_only=True)
class InputChannelData(ParameterDef, Metadata, ArrayData, RecordedData):
pass
@dataclass(frozen=True, kw_only=True)
class OutputChannelData(ParameterDef, Metadata, ArrayData, RecordedData):
def copyRecordedDataFrom(self, other: RecordedData) -> None:
...
@dataclass(frozen=True, kw_only=True)
class InputBinaryData(ParameterDef, Metadata, ArrayData, RecordedData):
pass
@dataclass(frozen=True, kw_only=True)
class OutputBinaryData(ParameterDef, Metadata, ArrayData, RecordedData):
def copyRecordedDataFrom(self, other: RecordedData) -> None:
...
@dataclass(frozen=True, kw_only=True)
class InputTableData(ParameterDef, RecordedData):
data: LimTableLike
def colArray(self,
cols: str|int|SafeColSpec = None
) -> ArrayLike|list[ArrayLike]:
"""Return numpy ndarray or a list there of for specified columns.
If cols is scalar ndarray is returned otherwise a list is returned instead.
Column specifier:
- column name, ID or index returns single column
- list of column names, IDs or indexes returns list of columns (must be present)
- None return all columns
- re.Pattern return columns where fullmatch succeeded on column ID or Title
- Callable[[str, dict], bool] returns columns where callable
returns True for (ID, Metadata)
When column names, IDs or indexes are specified explicitly in a list they must
be present otherwise and AssertError is raised.
Args:
cols: col name, id or index or a list there of or a Pattern or Callable
Returns:
Single 1D numpy ndarray or a list thereof.
Raises:
ValueError: if a column explicitly specified (by name, id or index)
is not found
TypeError: if cols are not of requested type
"""
...
def dataFrame(self,
*,
use_col_ids: bool = False,
no_hidden: bool = False,
no_loops: bool = False,
no_loop_indexes: bool = False,
no_bin: bool = False,
no_bin_id: bool = False,
no_bin_entity: bool = False,
no_well: bool = False,
no_file: bool = False,
excl: SafeColSpec = None,
incl: SafeColSpec = None) -> "pd.DataFrame":
"""Return Pandas DataFrame with all columns except as specified.
By default all columns are returned.
If no_* or excl kwarg is present, matching columns are removed.
If incl is present, matching columns are added (after exclusion)
Args:
use_col_ids: columns are indexed by column ids and not by column titles
no_*: exclude specific system column/hidden columns
excl: exclude any column
incl: include any column
Returns:
Pandas DataFrame.
Raises:
AssertError: if a column explicitly specified column (by name, id or index)
is not found
"""
...
def metaDataFrame(self) -> "pd.DataFrame":
...
@dataclass(frozen=True, kw_only=True)
class OutputTableData(ParameterDef, RecordedData):
data: LimTableLike|None
def withColDataFrom(self,
*args: Sequence[InputTableData|"pd.DataFrame"|"pd.Series"],
remaining_cols: list[str]|None = None
) -> OutputTableData:
...
def withColData(self,
cols: str|int|SafeColSpec,
data: MultiColData
) -> OutputTableData:
"""Fill table with column data and return self.
Column specifier:
- column name, ID or index returns single column
- list of column names, IDs or indexes returns list of columns (must be present)
- None return all columns
- re.Pattern return columns where fullmatch succeeded on column ID or Title
- Callable[[str, dict], bool] returns columns where callable returns
True for (ID, Metadata)
The data must have same length as cols.
When column names, IDs or indexes are specified explicitly in a list they must
be present otherwise and AssertError is raised.
Args:
cols: col name, id or index or a list there of or a Pattern or Callable
data: list of values, 1D nd array or a list of these or 2D nd array
Returns:
self
Raises:
ValueError: if a column explicitly specified column (by name, id or index)
is not found
TypeError: if cols or data are not of requested type
"""
...
def dataFrame(self, *, use_col_ids: bool = False) -> "pd.DataFrame":
"""Return Pandas DataFrame with all columns.
Args:
use_col_ids: columns are indexed by column ids and not by column titles
Returns:
Pandas DataFrame.
"""
...
def withDataFrame(self,
df: "pd.DataFrame",
cols: str|int|SafeColSpec = None
) -> OutputTableData:
"""Fill table with Pandas DataFrame.
Column specifier:
- column name, ID or index returns single column
- list of column names, IDs or indexes returns list of columns (must be present)
- None return all columns
- re.Pattern return columns where fullmatch succeeded on column ID or Title
- Callable[[str, dict], bool] returns columns where callable
returns True for (ID, Metadata)
When column names, IDs or indexes are specified explicitly in a list they must
be present otherwise and AssertError is raised.
Args:
df: Pandas DataFrame
cols: col name, id or index or a list there of or a Pattern or Callable
Returns:
self
Raises:
ValueError: if a column explicitly specified (by name, id or index)
is not found
TypeError: if cols or data are not of requested type
"""
...
def copyRecordedDataFrom(self, other: RecordedData) -> None:
...
def copyFrom(self, other: RecordedData) -> None:
...
def withMplImage(self, loopCoordinates: tuple[int],
plt: MplFigureLike|tuple[MplFigureLike,MplFigureLike],
*, name: str|None = None, iconres: str|None = None,
objectFit: str|None = None, tableRowVisibility: str|None = None,
savefig_kwargs: dict = { 'format': 'png' }
) -> OutputTableData:
...
def _makeResult(self,
data: LimTableLike,
jsClassName: str,
state: dict[str, Any] = {}) -> OutputTableData:
...The build(...) function
UserParTuple: TypeAlias = tuple[int|float|str, ...]
LoopDefs: TypeAlias = list[LoopDef]
def build(par: limnode.UserParTuple, loops: limnode.LoopDefs) -> limnode.Program|None:
return None@dataclass
class Program:
allLoopDefs: list[LoopDef]
overLoops: list[str] = field(default_factory=list, init=False)
def overZStack(self) -> Program:
...
def overTimeLapse(self) -> Program:
...
def overMultiPoint(self) -> Program:
...
def overAll(self) -> Program:
...
@dataclass
class ReduceProgram(Program):
pass
@dataclass
class TwoPassProgram(Program):
pass
@dataclass(kw_only=True)
class RunContext:
inpFilename: str
outFilename: str
inpParameterCoordinates: tuple[tuple[int]]
outCoordinates: tuple[int]
programLoopsOver: list[str]|None # loop names
programCoordinateIndexes: tuple[int]|None # inner loops: indexes of loops that must
# be handled in Python (not by GA3)
# e.g. Z-Stack loop
programPass: int
finalCall: bool
tempPath: str
@property
def shouldAbort(self) -> bool:
...
AnyColorDef: TypeAlias = int|str|tuple[int, int, int]
def _parse_color(color: AnyColorDef) -> int:
if type(color) == int:
return color & 0xFFFFFF
elif type(color) == str and len(color) == 7 and color[0] == '#':
return int(color[1:], 16)
elif type(color) == tuple:
assert 3 <= len(color), "Unexpected length of argument 'color'"
return (color[0] << 16) + (color[1] << 8) + color[2]
else:
return 0Utility functions
# labels to NIS binaries separated background pixel
def separateLabeledImage(src: ArrayLike) -> ArrayLike:
...
# labels to NIS binaries separated background voxls
def separateLabeledImage3d(src: ArrayLike) -> ArrayLike:
...