pymmcore-plus 0.10.1__py3-none-any.whl → 0.11.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. pymmcore_plus/__init__.py +4 -1
  2. pymmcore_plus/_build.py +2 -0
  3. pymmcore_plus/_cli.py +47 -12
  4. pymmcore_plus/_util.py +99 -9
  5. pymmcore_plus/core/__init__.py +2 -0
  6. pymmcore_plus/core/_constants.py +109 -8
  7. pymmcore_plus/core/_mmcore_plus.py +67 -47
  8. pymmcore_plus/core/events/_psygnal.py +2 -2
  9. pymmcore_plus/mda/__init__.py +2 -2
  10. pymmcore_plus/mda/_engine.py +148 -98
  11. pymmcore_plus/mda/_protocol.py +5 -3
  12. pymmcore_plus/mda/_runner.py +16 -21
  13. pymmcore_plus/mda/events/_protocol.py +10 -2
  14. pymmcore_plus/mda/events/_psygnal.py +2 -2
  15. pymmcore_plus/mda/handlers/_5d_writer_base.py +25 -13
  16. pymmcore_plus/mda/handlers/_img_sequence_writer.py +9 -5
  17. pymmcore_plus/mda/handlers/_ome_tiff_writer.py +7 -3
  18. pymmcore_plus/mda/handlers/_ome_zarr_writer.py +9 -4
  19. pymmcore_plus/mda/handlers/_tensorstore_handler.py +19 -19
  20. pymmcore_plus/metadata/__init__.py +36 -0
  21. pymmcore_plus/metadata/functions.py +343 -0
  22. pymmcore_plus/metadata/schema.py +471 -0
  23. pymmcore_plus/metadata/serialize.py +116 -0
  24. pymmcore_plus/model/_config_file.py +2 -4
  25. pymmcore_plus/model/_config_group.py +29 -3
  26. pymmcore_plus/model/_device.py +20 -1
  27. pymmcore_plus/model/_microscope.py +35 -1
  28. pymmcore_plus/model/_pixel_size_config.py +25 -3
  29. {pymmcore_plus-0.10.1.dist-info → pymmcore_plus-0.11.0.dist-info}/METADATA +4 -3
  30. pymmcore_plus-0.11.0.dist-info/RECORD +59 -0
  31. {pymmcore_plus-0.10.1.dist-info → pymmcore_plus-0.11.0.dist-info}/WHEEL +1 -1
  32. pymmcore_plus/core/_state.py +0 -244
  33. pymmcore_plus-0.10.1.dist-info/RECORD +0 -56
  34. {pymmcore_plus-0.10.1.dist-info → pymmcore_plus-0.11.0.dist-info}/entry_points.txt +0 -0
  35. {pymmcore_plus-0.10.1.dist-info → pymmcore_plus-0.11.0.dist-info}/licenses/LICENSE +0 -0
pymmcore_plus/__init__.py CHANGED
@@ -9,7 +9,7 @@ except PackageNotFoundError: # pragma: no cover
9
9
 
10
10
 
11
11
  from ._logger import configure_logging
12
- from ._util import find_micromanager
12
+ from ._util import find_micromanager, use_micromanager
13
13
  from .core import (
14
14
  CFGCommand,
15
15
  CFGGroup,
@@ -26,6 +26,7 @@ from .core import (
26
26
  FocusDirection,
27
27
  Keyword,
28
28
  Metadata,
29
+ PixelFormat,
29
30
  PortType,
30
31
  PropertyType,
31
32
  )
@@ -55,6 +56,8 @@ __all__ = [
55
56
  "Keyword",
56
57
  "Metadata",
57
58
  "PCoreSignaler",
59
+ "PixelFormat",
58
60
  "PortType",
59
61
  "PropertyType",
62
+ "use_micromanager",
60
63
  ]
pymmcore_plus/_build.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """Clone the micro-manager source code from GitHub and build dev devices."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import json
4
6
  import os
5
7
  import platform
pymmcore_plus/_cli.py CHANGED
@@ -18,11 +18,12 @@ except ImportError: # pragma: no cover
18
18
  ) from None
19
19
 
20
20
  import pymmcore_plus
21
+ from pymmcore_plus._build import DEFAULT_PACKAGES, build
21
22
  from pymmcore_plus._logger import configure_logging
22
23
  from pymmcore_plus._util import USER_DATA_MM_PATH
23
24
  from pymmcore_plus.install import PLATFORM
24
25
 
25
- app = typer.Typer(no_args_is_help=True)
26
+ app = typer.Typer(name="mmcore", no_args_is_help=True)
26
27
 
27
28
 
28
29
  def _show_version_and_exit(value: bool) -> None:
@@ -47,7 +48,7 @@ def _main(
47
48
  ) -> None:
48
49
  """mmcore: pymmcore-plus command line (v{version}).
49
50
 
50
- For additional help on a specific command: type 'mmcore [command] --help'
51
+ For additional help on a specific command: type `mmcore [command] --help`
51
52
  """
52
53
  # fix for windows CI encoding and emoji printing
53
54
  if getattr(sys.stdout, "encoding", None) != "utf-8":
@@ -55,10 +56,13 @@ def _main(
55
56
  sys.stdout.reconfigure(encoding="utf-8") # type: ignore [attr-defined]
56
57
 
57
58
 
58
- _main.__doc__ = typer.style(
59
- (_main.__doc__ or "").format(version=pymmcore_plus.__version__),
60
- fg=typer.colors.BRIGHT_YELLOW,
61
- )
59
+ if "mkdocs" in sys.argv[0]: # pragma: no cover
60
+ _main.__doc__ = (_main.__doc__ or "").replace(" (v{version})", "")
61
+ else:
62
+ _main.__doc__ = typer.style(
63
+ (_main.__doc__ or "").format(version=pymmcore_plus.__version__),
64
+ fg=typer.colors.BRIGHT_YELLOW,
65
+ )
62
66
 
63
67
 
64
68
  @app.command()
@@ -91,7 +95,8 @@ def _list() -> None:
91
95
  print(f":file_folder:[bold green] {parent}")
92
96
  for item in items:
93
97
  bullet = " [bold yellow]*" if first else " •"
94
- print(f"{bullet} [cyan]{item}")
98
+ using = " [bold blue](active)" if first else ""
99
+ print(f"{bullet} [cyan]{item}{using}")
95
100
  first = False
96
101
  else:
97
102
  print(":x: [bold red]There are no pymmcore-plus Micro-Manager files.")
@@ -100,7 +105,11 @@ def _list() -> None:
100
105
 
101
106
  @app.command()
102
107
  def mmstudio() -> None: # pragma: no cover
103
- """Run the Java Micro-Manager GUI."""
108
+ """Run the Java Micro-Manager GUI.
109
+
110
+ This command will attempt to locate an execute an ImageJ application found in
111
+ the active Micro-Manager directory.
112
+ """
104
113
  mm = pymmcore_plus.find_micromanager()
105
114
  app = (
106
115
  next((x for x in Path(mm).glob("ImageJ*") if not str(x).endswith("cfg")), None)
@@ -137,7 +146,7 @@ def install(
137
146
  show_default=False,
138
147
  ),
139
148
  ) -> None:
140
- """Install Micro-Manager Device adapters."""
149
+ """Install Micro-Manager Device adapters from <https://download.micro-manager.org>."""
141
150
  import pymmcore_plus.install
142
151
 
143
152
  if plain_output:
@@ -286,7 +295,7 @@ def run(
286
295
  @app.command()
287
296
  def build_dev(
288
297
  devices: Optional[List[str]] = typer.Argument(
289
- None, help="Device adapters to build. Defaults to DemoCamera and Utilities."
298
+ None, help=f"Device adapters to build. Defaults to {DEFAULT_PACKAGES}"
290
299
  ),
291
300
  dest: Path = typer.Option(
292
301
  USER_DATA_MM_PATH,
@@ -305,9 +314,10 @@ def build_dev(
305
314
  "If not specified, will prompt.",
306
315
  ),
307
316
  ) -> None: # pragma: no cover
308
- """Build DemoCamera and Utility adapters from source for apple silicon."""
309
- from pymmcore_plus._build import DEFAULT_PACKAGES, build
317
+ """Build Micro-Manager device adapters from the git repo.
310
318
 
319
+ Currently only supports macos and linux.
320
+ """
311
321
  devices = DEFAULT_PACKAGES if not devices else devices
312
322
  try:
313
323
  build(dest, overwrite=overwrite, devices=devices)
@@ -374,6 +384,31 @@ def info() -> None:
374
384
  typer.secho(f"{key:{length}}: {value}")
375
385
 
376
386
 
387
+ @app.command()
388
+ def use(
389
+ pattern: str = typer.Argument(
390
+ ...,
391
+ help="Path to an existing directory, or pattern to match against installations "
392
+ "found by `mmcore list`",
393
+ ),
394
+ ) -> None:
395
+ """Change the currently used Micro-manager version/path."""
396
+ from pymmcore_plus._util import use_micromanager
397
+
398
+ _pth = Path(pattern)
399
+ if _pth.exists():
400
+ if not _pth.is_dir():
401
+ raise typer.BadParameter("must be a directory")
402
+ result = use_micromanager(path=_pth)
403
+ else:
404
+ try:
405
+ result = use_micromanager(pattern=pattern)
406
+ except FileNotFoundError as e:
407
+ raise typer.BadParameter(str(e)) from None
408
+
409
+ typer.secho(f"using {result}", fg=typer.colors.BRIGHT_GREEN)
410
+
411
+
377
412
  def _tail_file(file_path: Union[str, Path], interval: float = 0.1) -> None:
378
413
  with open(file_path) as file:
379
414
  # Move the file pointer to the end
pymmcore_plus/_util.py CHANGED
@@ -1,8 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import datetime
3
4
  import importlib
4
5
  import os
5
6
  import platform
7
+ import re
6
8
  import sys
7
9
  import warnings
8
10
  from collections import defaultdict
@@ -16,6 +18,7 @@ from typing import TYPE_CHECKING, cast, overload
16
18
  from platformdirs import user_data_dir
17
19
 
18
20
  if TYPE_CHECKING:
21
+ from re import Pattern
19
22
  from typing import Any, Callable, Iterator, Literal, TypeVar
20
23
 
21
24
  QtConnectionType = Literal["AutoConnection", "DirectConnection", "QueuedConnection"]
@@ -40,6 +43,7 @@ __all__ = ["find_micromanager", "retry", "no_stdout", "signals_backend"]
40
43
  APP_NAME = "pymmcore-plus"
41
44
  USER_DATA_DIR = Path(user_data_dir(appname=APP_NAME))
42
45
  USER_DATA_MM_PATH = USER_DATA_DIR / "mm"
46
+ CURRENT_MM_PATH = USER_DATA_MM_PATH / ".current_mm"
43
47
  PYMMCORE_PLUS_PATH = Path(__file__).parent.parent
44
48
 
45
49
 
@@ -57,16 +61,17 @@ def find_micromanager(return_first: bool = True) -> str | None | list[str]:
57
61
  In order, this will look for:
58
62
 
59
63
  1. An environment variable named `MICROMANAGER_PATH`
60
- 2. A `Micro-Manager*` folder in the `pymmcore-plus` user data directory
64
+ 2. A path stored in the `CURRENT_MM_PATH` file (set by `use_micromanager`).
65
+ 3. A `Micro-Manager*` folder in the `pymmcore-plus` user data directory
61
66
  (this is the default install location when running `mmcore install`)
62
67
 
63
68
  - **Windows**: C:\Users\\[user]\AppData\Local\pymmcore-plus\pymmcore-plus
64
69
  - **macOS**: ~/Library/Application Support/pymmcore-plus
65
70
  - **Linux**: ~/.local/share/pymmcore-plus
66
71
 
67
- 3. A `Micro-Manager*` folder in the `pymmcore_plus` package directory (this is the
72
+ 4. A `Micro-Manager*` folder in the `pymmcore_plus` package directory (this is the
68
73
  default install location when running `python -m pymmcore_plus.install`)
69
- 4. The default micro-manager install location:
74
+ 5. The default micro-manager install location:
70
75
 
71
76
  - **Windows**: `C:/Program Files/`
72
77
  - **macOS**: `/Applications`
@@ -88,14 +93,25 @@ def find_micromanager(return_first: bool = True) -> str | None | list[str]:
88
93
  """
89
94
  from ._logger import logger
90
95
 
96
+ # we use a dict here to avoid duplicates
97
+ full_list: dict[str, None] = {}
98
+
91
99
  # environment variable takes precedence
92
- full_list: list[str] = []
93
100
  env_path = os.getenv("MICROMANAGER_PATH")
94
101
  if env_path and os.path.isdir(env_path):
95
102
  if return_first:
96
103
  logger.debug("using MM path from env var: %s", env_path)
97
104
  return env_path
98
- full_list.append(env_path)
105
+ full_list[env_path] = None
106
+
107
+ # then check for a path in CURRENT_MM_PATH
108
+ if CURRENT_MM_PATH.exists():
109
+ path = CURRENT_MM_PATH.read_text().strip()
110
+ if os.path.isdir(path):
111
+ if return_first:
112
+ logger.debug("using MM path from current_mm: %s", path)
113
+ return path
114
+ full_list[path] = None
99
115
 
100
116
  # then look in user_data_dir
101
117
  _folders = (p for p in USER_DATA_MM_PATH.glob("Micro-Manager*") if p.is_dir())
@@ -104,7 +120,8 @@ def find_micromanager(return_first: bool = True) -> str | None | list[str]:
104
120
  if return_first:
105
121
  logger.debug("using MM path from user install: %s", user_install[0])
106
122
  return str(user_install[0])
107
- full_list.extend([str(x) for x in user_install])
123
+ for x in user_install:
124
+ full_list[str(x)] = None
108
125
 
109
126
  # then look for an installation in this folder (from `pymmcore_plus.install`)
110
127
  sfx = "_win" if os.name == "nt" else "_mac"
@@ -115,7 +132,8 @@ def find_micromanager(return_first: bool = True) -> str | None | list[str]:
115
132
  if return_first:
116
133
  logger.debug("using MM path from local install: %s", local_install[0])
117
134
  return str(local_install[0])
118
- full_list.extend([str(x) for x in local_install])
135
+ for x in local_install:
136
+ full_list[str(x)] = None
119
137
 
120
138
  applications = {
121
139
  "darwin": Path("/Applications/"),
@@ -137,8 +155,59 @@ def find_micromanager(return_first: bool = True) -> str | None | list[str]:
137
155
  logger.debug("using MM path found in applications: %s", pth)
138
156
  return str(pth)
139
157
  if pth is not None:
140
- full_list.append(str(pth))
141
- return full_list
158
+ full_list[str(pth)] = None
159
+ return list(full_list)
160
+
161
+
162
+ def _match_mm_pattern(pattern: str | Pattern[str]) -> Path | None:
163
+ """Locate an existing Micro-Manager folder using a regex pattern."""
164
+ for _path in find_micromanager(return_first=False):
165
+ if not isinstance(pattern, re.Pattern):
166
+ pattern = str(pattern)
167
+ if re.search(pattern, _path) is not None:
168
+ return Path(_path)
169
+ return None
170
+
171
+
172
+ def use_micromanager(
173
+ path: str | Path | None = None, pattern: str | Pattern[str] | None = None
174
+ ) -> Path | None:
175
+ """Set the preferred Micro-Manager path.
176
+
177
+ This sets the preferred micromanager path, and persists across sessions.
178
+ This path takes precedence over everything *except* the `MICROMANAGER_PATH`
179
+ environment variable.
180
+
181
+ Parameters
182
+ ----------
183
+ path : str | Path | None
184
+ Path to an existing directory. This directory should contain micro-manager
185
+ device adapters. If `None`, the path will be determined using `pattern`.
186
+ pattern : str Pattern | | None
187
+ A regex pattern to match against the micromanager paths found by
188
+ `find_micromanager`. If no match is found, a `FileNotFoundError` will be raised.
189
+ """
190
+ if path is None:
191
+ if pattern is None: # pragma: no cover
192
+ raise ValueError("One of 'path' or 'pattern' must be provided")
193
+ if (path := _match_mm_pattern(pattern)) is None:
194
+ options = "\n".join(find_micromanager(return_first=False))
195
+ raise FileNotFoundError(
196
+ f"No micromanager path found matching: {pattern!r}. Options:\n{options}"
197
+ )
198
+
199
+ if not isinstance(path, Path): # pragma: no cover
200
+ path = Path(path)
201
+
202
+ path = path.expanduser().resolve()
203
+ if not path.is_dir(): # pragma: no cover
204
+ if not path.exists():
205
+ raise FileNotFoundError(f"Path not found: {path!r}")
206
+ raise NotADirectoryError(f"Not a directory: {path!r}")
207
+
208
+ USER_DATA_MM_PATH.mkdir(parents=True, exist_ok=True)
209
+ CURRENT_MM_PATH.write_text(str(path))
210
+ return path
142
211
 
143
212
 
144
213
  def _imported_qt_modules() -> Iterator[str]:
@@ -521,3 +590,24 @@ def system_info() -> dict[str, str]:
521
590
  info["qt"] = f"{API_NAME} {QT_VERSION}"
522
591
 
523
592
  return info
593
+
594
+
595
+ if sys.version_info < (3, 11):
596
+
597
+ def _utcnow() -> datetime.datetime:
598
+ return datetime.datetime.utcnow()
599
+ else:
600
+
601
+ def _utcnow() -> datetime.datetime:
602
+ return datetime.datetime.now(datetime.UTC)
603
+
604
+
605
+ def timestamp() -> str:
606
+ """Return the current timestamp, try using local timezone, in ISO format.
607
+
608
+ YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM
609
+ """
610
+ now = _utcnow()
611
+ with suppress(Exception):
612
+ now = now.astimezone()
613
+ return now.isoformat()
@@ -15,6 +15,7 @@ __all__ = [
15
15
  "FocusDirection",
16
16
  "Keyword",
17
17
  "Metadata",
18
+ "PixelFormat",
18
19
  "PortType",
19
20
  "PropertyType",
20
21
  ]
@@ -32,6 +33,7 @@ from ._constants import (
32
33
  DeviceType,
33
34
  FocusDirection,
34
35
  Keyword,
36
+ PixelFormat,
35
37
  PortType,
36
38
  PropertyType,
37
39
  )
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from enum import Enum, IntEnum
4
+ from typing import Literal
4
5
 
5
6
  import pymmcore
6
7
 
@@ -163,8 +164,8 @@ class PropertyType(IntEnum):
163
164
  def to_json(self) -> str:
164
165
  return {0: "null", 1: "string", 2: "number", 3: "integer"}[self]
165
166
 
166
- def __repr__(self) -> str:
167
- return getattr(self.to_python(), "__name__", "None")
167
+ def __repr__(self) -> Literal["undefined", "float", "int", "str"]:
168
+ return getattr(self.to_python(), "__name__", "undefined")
168
169
 
169
170
 
170
171
  class ActionType(IntEnum):
@@ -185,13 +186,13 @@ class PortType(IntEnum):
185
186
 
186
187
 
187
188
  class FocusDirection(IntEnum):
188
- FocusDirectionUnknown = pymmcore.FocusDirectionUnknown
189
- FocusDirectionTowardSample = pymmcore.FocusDirectionTowardSample
190
- FocusDirectionAwayFromSample = pymmcore.FocusDirectionAwayFromSample
189
+ Unknown = pymmcore.FocusDirectionUnknown
190
+ TowardSample = pymmcore.FocusDirectionTowardSample
191
+ AwayFromSample = pymmcore.FocusDirectionAwayFromSample
191
192
  # aliases
192
- Unknown = FocusDirectionUnknown
193
- TowardSample = FocusDirectionTowardSample
194
- AwayFromSample = FocusDirectionAwayFromSample
193
+ FocusDirectionUnknown = Unknown
194
+ FocusDirectionTowardSample = TowardSample
195
+ FocusDirectionAwayFromSample = AwayFromSample
195
196
 
196
197
 
197
198
  class DeviceNotification(IntEnum):
@@ -222,6 +223,12 @@ class DeviceInitializationState(IntEnum):
222
223
 
223
224
 
224
225
  class PixelType(str, Enum):
226
+ """These are pixel types, as used in MMStudio and MMCoreJ wrapper.
227
+
228
+ They are only here for supporting the legacy (and probably to-be-deprecated)
229
+ taggedImages.
230
+ """
231
+
225
232
  UNKNOWN = ""
226
233
  GRAY8 = "GRAY8"
227
234
  GRAY16 = "GRAY16"
@@ -239,3 +246,97 @@ class PixelType(str, Enum):
239
246
  depth, cls.UNKNOWN
240
247
  )
241
248
  return cls.GRAY32 if n_comp == 1 else cls.RGB32
249
+
250
+ def to_pixel_format(self) -> PixelFormat:
251
+ return {
252
+ self.GRAY8: PixelFormat.MONO8,
253
+ self.GRAY16: PixelFormat.MONO16,
254
+ self.GRAY32: PixelFormat.MONO32,
255
+ self.RGB32: PixelFormat.RGB8,
256
+ self.RGB64: PixelFormat.RGB16,
257
+ }[self]
258
+
259
+
260
+ class PixelFormat(str, Enum):
261
+ """Subset of GeniCam Pixel Format names used by pymmcore-plus.
262
+
263
+ (This is similar to PixelType, but follows GeniCam standards.)
264
+
265
+ See <https://docs.baslerweb.com/pixel-format#unpacked-and-packed-pixel-formats>
266
+ for helpful clarifications. Note that **unpacked** pixel formats (like
267
+ Mono8, Mono12, Mono16) are always 8-bit aligned. Meaning Mono12 is actually
268
+ a 16-bit buffer.
269
+
270
+ Attributes
271
+ ----------
272
+ MONO8 : str
273
+ 8-bit (unpacked) monochrome pixel format.
274
+ MONO10 : str
275
+ 10-bit (unpacked) monochrome pixel format. (16-bit buffer)
276
+ MONO12 : str
277
+ 12-bit (unpacked) monochrome pixel format. (16-bit buffer)
278
+ MONO14 : str
279
+ 14-bit (unpacked) monochrome pixel format. (16-bit buffer)
280
+ MONO16 : str
281
+ 16-bit (unpacked) monochrome pixel format
282
+ MONO32 : str
283
+ 32-bit (unpacked) monochrome pixel format
284
+ RGB8 : str
285
+ 8-bit RGB pixel format. (24-bit buffer)
286
+ RGB10 : str
287
+ 10-bit RGB pixel format. (48-bit buffer)
288
+ RGB12 : str
289
+ 12-bit RGB pixel format. (48-bit buffer)
290
+ RGB14 : str
291
+ 14-bit RGB pixel format. (48-bit buffer)
292
+ RGB16 : str
293
+ 16-bit RGB pixel format. (48-bit buffer)
294
+ """
295
+
296
+ MONO8 = "Mono8"
297
+ MONO10 = "Mono10"
298
+ MONO12 = "Mono12"
299
+ MONO14 = "Mono14"
300
+ MONO16 = "Mono16"
301
+ MONO32 = "Mono32"
302
+ RGB8 = "RGB8"
303
+ RGB10 = "RGB10"
304
+ RGB12 = "RGB12"
305
+ RGB14 = "RGB14"
306
+ RGB16 = "RGB16"
307
+
308
+ @classmethod
309
+ def pick(cls, bit_depth: int, n_comp: int = 1) -> PixelFormat:
310
+ try:
311
+ return PIXEL_FORMATS[n_comp][bit_depth]
312
+ except KeyError as e:
313
+ raise NotImplementedError(
314
+ f"Unsupported Pixel Format {bit_depth=} {n_comp=}"
315
+ ) from e
316
+
317
+ @classmethod
318
+ def for_current_camera(cls, core: pymmcore.CMMCore) -> PixelFormat:
319
+ n_comp = core.getNumberOfComponents()
320
+ if n_comp == 4:
321
+ n_comp = 3
322
+ return cls.pick(core.getImageBitDepth(), n_comp)
323
+
324
+
325
+ # map of {number of components: {bit depth: PixelFormat}}
326
+ PIXEL_FORMATS: dict[int, dict[int, PixelFormat]] = {
327
+ 1: {
328
+ 8: PixelFormat.MONO8,
329
+ 10: PixelFormat.MONO10,
330
+ 12: PixelFormat.MONO12,
331
+ 14: PixelFormat.MONO14,
332
+ 16: PixelFormat.MONO16,
333
+ 32: PixelFormat.MONO32,
334
+ },
335
+ 3: {
336
+ 8: PixelFormat.RGB8,
337
+ 10: PixelFormat.RGB10,
338
+ 12: PixelFormat.RGB12,
339
+ 14: PixelFormat.RGB14,
340
+ 16: PixelFormat.RGB16,
341
+ },
342
+ }