pigeon-tem-comms 1.4.1__py3-none-any.whl → 2.1.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.
TEM_comms/__init__.py CHANGED
@@ -1,9 +1,17 @@
1
- from pigeon import BaseMessage
2
- from . import buffer, camera, montage, qc, roi, scope, stage, tile, ui, calibration
3
-
4
-
5
- class State(BaseMessage):
6
- state: str
1
+ from . import (
2
+ buffer,
3
+ camera,
4
+ montage,
5
+ qc,
6
+ roi,
7
+ scope,
8
+ stage,
9
+ tile,
10
+ ui,
11
+ calibration,
12
+ state,
13
+ lens_correction,
14
+ )
7
15
 
8
16
 
9
17
  topics = {
@@ -14,6 +22,9 @@ topics = {
14
22
  "camera.status": camera.Status,
15
23
  "scope.command": scope.Command,
16
24
  "scope.status": scope.Status,
25
+ "scope.image_shift.status": scope.image_shift.Status,
26
+ "scope.image_shift.command": scope.image_shift.Command,
27
+ "scope.image_shift.calibrate": scope.image_shift.Calibrate,
17
28
  "stage.aperture.command": stage.aperture.Command,
18
29
  "stage.aperture.status": stage.aperture.Status,
19
30
  "stage.motion.command": stage.motion.Command,
@@ -28,6 +39,7 @@ topics = {
28
39
  "tile.statistics.histogram": tile.statistics.Histogram,
29
40
  "tile.statistics.min_max_mean": tile.statistics.MinMaxMean,
30
41
  "tile.matches": tile.Matches,
42
+ "tile.template_matches": tile.TemplateMatches,
31
43
  "ui.edit": ui.Edit,
32
44
  "ui.run": ui.Run,
33
45
  "ui.setup": ui.Setup,
@@ -40,5 +52,7 @@ topics = {
40
52
  "montage.minimaps": montage.Minimaps,
41
53
  "calibration.resolution": calibration.Resolution,
42
54
  "calibration.centroid": calibration.Centroid,
43
- "state": State,
55
+ "state.current": state.Current,
56
+ "state.change": state.Change,
57
+ "lens_correction.transform": lens_correction.Transform,
44
58
  }
TEM_comms/buffer.py CHANGED
@@ -1,7 +1,18 @@
1
1
  from pigeon import BaseMessage
2
+ from pydantic import Field
2
3
 
3
4
 
4
5
  class Status(BaseMessage):
5
- queue_length: int
6
- free_space: int
7
- upload_rate: int
6
+ """
7
+ This message contains information about the status of the tile upload buffer.
8
+ """
9
+
10
+ queue_length: int = Field(
11
+ description="The number of tiles currently stored and waiting to be uploaded."
12
+ )
13
+ free_space: int = Field(
14
+ description="The amount of free space on the tile storage disk in bytes."
15
+ )
16
+ upload_rate: int = Field(
17
+ description="The rate at which data is being uploaded in bits per second."
18
+ )
TEM_comms/calibration.py CHANGED
@@ -1,18 +1,42 @@
1
1
  from pigeon import BaseMessage
2
2
  from typing import Mapping
3
+ from pydantic import Field
3
4
 
4
5
 
5
6
  class _Resolution(BaseMessage):
6
- nm_per_px: tuple[float, float]
7
- rotation: float
7
+ nm_per_px: tuple[float, float] = Field(
8
+ description="The X-Y size of each pixel in the processed camera image in nanometers.",
9
+ examples=[(3.97, 4.03)],
10
+ )
11
+ rotation: float = Field(
12
+ description="The rotation required to match a right-handed image coordinate system to the coordinate system of the stage in radians."
13
+ )
8
14
 
9
15
 
10
16
  class Resolution(BaseMessage):
11
- lowmag: Mapping[int, _Resolution]
12
- mag: Mapping[int, _Resolution]
17
+ """
18
+ This message contains the resolutions of all the calibrated magnifications of the microscope.
19
+ """
20
+
21
+ lowmag: Mapping[int, _Resolution] = Field(
22
+ description="A mapping of calibrated low-mag magnifications to thier calibration values."
23
+ )
24
+ mag: Mapping[int, _Resolution] = Field(
25
+ description="A mapping of calibrated high-mag magnifications to their calibration values."
26
+ )
13
27
 
14
28
 
15
29
  class Centroid(BaseMessage):
16
- aperture_id: int
17
- x: int
18
- y: int
30
+ """
31
+ This message contains the location of the centroid of the specified aperture.
32
+ """
33
+
34
+ aperture_id: int = Field(
35
+ description="The ID of the aperture that this centroid pertains to."
36
+ )
37
+ x: int = Field(
38
+ description="The stage coordinate system X axis location of the centroid."
39
+ )
40
+ y: int = Field(
41
+ description="The stage coordinate system Y axis location of the centroid."
42
+ )
TEM_comms/camera.py CHANGED
@@ -1,34 +1,64 @@
1
1
  from pigeon import BaseMessage
2
- from .tile.metadata import TileMetadata
2
+ from .tile.metadata import TileMetadata, ProcessingOptions
3
3
  from typing import Optional
4
+ from pydantic import Field
4
5
 
5
6
 
6
- class Command(TileMetadata):
7
- brightfield: Optional[bool] = False
8
- darkfield: Optional[bool] = False
9
- lens_correction: Optional[bool] = True
7
+ class Command(TileMetadata, ProcessingOptions):
8
+ """
9
+ This message is used to instruct the camera to capture an image.
10
+ """
10
11
 
11
12
 
12
13
  class Image(BaseMessage):
13
- tile_id: str
14
- montage_id: str
14
+ """
15
+ This message is sent after the camera exposure is complete.
16
+ """
17
+
18
+ tile_id: str = Field(
19
+ description="The tile ID", examples=["69005602-15b0-4407-bf5b-4bddd6629141"]
20
+ )
21
+ montage_id: str = Field(
22
+ description="The montage ID. If a zero length string, the tile is for UI display or calibration purposes only.",
23
+ examples=["4330c7cf-e45b-4950-89cf-82dc0f815fe9"],
24
+ )
15
25
 
16
26
 
17
27
  class Settings(BaseMessage):
18
- exposure: float | None = None
19
- gain: Optional[float] = None
20
- width: int | None = None
21
- height: int | None = None
28
+ """
29
+ This message is used to change camera settings.
30
+ """
31
+
32
+ exposure: float | None = Field(
33
+ default=None, description="The length of the camera exposure in microseconds."
34
+ )
35
+ gain: Optional[float] = Field(
36
+ default=None, description="The camera gain in decibels."
37
+ )
38
+ width: int | None = Field(
39
+ default=None, description="The width of the camera frame."
40
+ )
41
+ height: int | None = Field(
42
+ default=None, description="The height of the camera frame."
43
+ )
22
44
 
23
45
 
24
46
  class Status(BaseMessage):
25
- exposure: float
26
- gain: float
27
- width: int
28
- height: int
29
- temp: float
30
- target_temp: float
31
- device_name: str
32
- device_model_id: int
33
- device_sn: str
34
- bit_depth: int | str
47
+ """
48
+ This message contains information about the state of the camera.
49
+ """
50
+
51
+ exposure: float = Field(
52
+ description="The length of the camera exposure in microseconds."
53
+ )
54
+ gain: float = Field(description="The camera gain in decibels.")
55
+ width: int = Field(description="The camera frame width.")
56
+ height: int = Field(description="The camera frame height.")
57
+ temp: float = Field(description="The camera temperature.")
58
+ target_temp: float = Field(
59
+ description="The target temperature of the camera cooling system."
60
+ )
61
+ device_name: str = Field(description="The human readable device name.")
62
+ device_model_id: str = Field(description="The device model identifier.")
63
+ device_sn: str = Field(description="The camera serial number.")
64
+ bit_depth: int | str = Field(description="The bit depth of the camera sensor.")
@@ -0,0 +1,10 @@
1
+ from pigeon import BaseMessage
2
+ from pydantic import Field
3
+
4
+
5
+ class Transform(BaseMessage):
6
+ """
7
+ This message contains a compact representation of the generated lens correction transform.
8
+ """
9
+
10
+ transform: str = Field(description="The base64 encoded lens correction transform.")
TEM_comms/montage.py CHANGED
@@ -1,32 +1,55 @@
1
1
  from pigeon import BaseMessage
2
2
  from typing import Mapping, Dict, Any, Optional, Tuple
3
3
  from datetime import datetime
4
+ from pydantic import Field
4
5
 
5
6
 
6
7
  class Tile(BaseMessage):
7
- raster_index: int
8
- stage_position: Tuple[int, int]
9
- raster_position: Tuple[int, int]
8
+ raster_index: int = Field(
9
+ description="The index of the tile incremented for each subsequent tile in the montage."
10
+ )
11
+ stage_position: Tuple[int, int] = Field(
12
+ description="The X-Y stage position of the tile in nanometers."
13
+ )
14
+ raster_position: Tuple[int, int] = Field(
15
+ description="The X-Y indicies of the tile in the montage."
16
+ )
10
17
 
11
18
 
12
19
  class Complete(BaseMessage):
13
- montage_id: str
14
- tiles: Dict[str, Tile]
15
- acquisition_id: str
16
- start_time: datetime
17
- pixel_size: float
18
- rotation_angle: float
19
- aperture_centroid: Tuple[int, int]
20
+ """
21
+ This message is sent after a montage is completed.
22
+ """
23
+
24
+ montage_id: str = Field(description="The unique montage ID.")
25
+ tiles: Dict[str, Tile] = Field(
26
+ description="A mapping from tile IDs to tile metadata."
27
+ )
28
+ acquisition_id: str = Field(description="The corresponding TEMdb acqusition ID.")
29
+ start_time: datetime = Field(
30
+ description="The timestamp when the montage was started."
31
+ )
32
+ pixel_size: float = Field(description="The average size ofee")
33
+ rotation_angle: float = Field(
34
+ description="The necessary tile rotation in radians to line up the right-handed image coordinate system with the Stage x-y coordinate system."
35
+ )
36
+ aperture_centroid: Tuple[int, int] = Field(
37
+ description="The X-Y coordinates of the centroid of the imaged aperture in nanometers."
38
+ )
20
39
 
21
40
 
22
41
  class Minimap(BaseMessage):
23
- image: Optional[str]
24
- colorbar: str
25
- min: Optional[float]
26
- max: Optional[float]
42
+ image: Optional[str] = Field(description="The map as a base 64 encoded image.")
43
+ colorbar: str = Field(description="The colorbar as a base 64 encoded image.")
44
+ min: Optional[float] = Field(description="The minimum value of the colorbar.")
45
+ max: Optional[float] = Field(description="The maximum value of the colorbar.")
27
46
 
28
47
 
29
48
  class Minimaps(BaseMessage):
30
- montage_id: str
31
- montage: Minimap
32
- focus: Minimap
49
+ """
50
+ This message contains multiple overview maps including a low resolution version of the montage, along with various statistics.
51
+ """
52
+
53
+ montage_id: str = Field(description="The unique montage ID.")
54
+ montage: Minimap = Field(description="The montage minimap.")
55
+ focus: Minimap = Field(description="The focus score map.")
TEM_comms/qc.py CHANGED
@@ -1,6 +1,17 @@
1
1
  from pigeon import BaseMessage
2
- from typing import Literal
2
+ from typing import Literal, Optional
3
+ from pydantic import Field
3
4
 
4
5
 
5
6
  class Status(BaseMessage):
6
- state: Literal["GOOD", "STOP_AT_END", "STOP_NOW"]
7
+ """
8
+ This message contains the overall QC status of the system.
9
+ """
10
+
11
+ status: Literal["GOOD", "STOP_AT_END", "STOP_NOW"] = Field(
12
+ description='The QC status, "GOOD" if imaging should continue, "STOP_AT_END" if imaging should be stopped at the end of the current montage, and "STOP_NOW" if imaging should be stopped immediately.'
13
+ )
14
+ reason: Optional[str] = Field(
15
+ default=None,
16
+ description="Optionally, a human readable reason for the current status.",
17
+ )
TEM_comms/roi.py CHANGED
@@ -9,30 +9,61 @@ class Vertex(BaseMessage):
9
9
 
10
10
 
11
11
  class ROI(BaseMessage):
12
- vertices: List[Vertex]
13
- rotation_angle: float
14
- buffer_size: float = 0.0
15
- montage_id: str
16
- specimen_id: Optional[str] = None
17
- grid_id: Optional[str] = None
18
- section_id: Optional[str] = None
19
- metadata: Optional[dict] = None
12
+ """
13
+ Information about the current ROI being imaged.
14
+ """
15
+
16
+ vertices: List[Vertex] = Field(
17
+ description="A list of points defining a polygonal ROI."
18
+ )
19
+ rotation_angle: float = Field(
20
+ description="The rotation angle of the ROI in radians."
21
+ )
22
+ buffer_size: float = Field(
23
+ default=0.0,
24
+ description="The amount to dialate the ROI in nanometers when imaging.",
25
+ )
26
+ montage_id: str = Field(description="The montage ID to use when imaging the ROI.")
27
+ specimen_id: Optional[str] = Field(
28
+ default=None, description="The specimen ID corresponding to this ROI."
29
+ )
30
+ grid_id: Optional[str] = Field(
31
+ default=None, description="The grid ID where this ROI should be imaged."
32
+ )
33
+ section_id: Optional[str] = Field(
34
+ default=None, description="The section ID corresponding to this ROI."
35
+ )
36
+ metadata: Optional[dict] = Field(
37
+ default=None, description="Extra metadata about this ROI."
38
+ )
20
39
  queue_position: Optional[int] = Field(
21
40
  None, description="Position in queue, None means set as current"
22
41
  )
23
42
 
24
43
 
25
44
  class LoadROI(BaseMessage):
26
- specimen_id: str
27
- section_id: str
28
- grid_id: Optional[str] = None
45
+ """
46
+ This message can be used to load an ROI from the database into the ROI queue.
47
+ """
48
+
49
+ specimen_id: str = Field(description="The specimen ID to load from the database.")
50
+ section_id: str = Field(description="The section ID to load from the database.")
51
+ grid_id: Optional[str] = Field(
52
+ default=None, description="The the grid ID where the section is loaded."
53
+ )
29
54
  queue_position: Optional[int] = Field(
30
55
  None, description="Position in queue, None means set as current"
31
56
  )
32
57
 
33
58
 
34
59
  class CreateROI(ROI):
35
- center: Optional[Vertex] = None
60
+ """
61
+ This message can be used to create an ROI and add it to the ROI queue.
62
+ """
63
+
64
+ center: Optional[Vertex] = Field(
65
+ default=None, description="The center point of the ROI."
66
+ )
36
67
  tilt_angles: Optional[List[float]] = Field(
37
68
  default=[0.0],
38
69
  description="List of tilt angles in degrees for tomography series",
@@ -49,6 +80,10 @@ class CreateROI(ROI):
49
80
 
50
81
 
51
82
  class ROIStatus(BaseMessage):
83
+ """
84
+ This message contains information on the status of an individual ROI.
85
+ """
86
+
52
87
  type: str = Field(
53
88
  description="Event type: roi_added, roi_advanced, queue_cleared, queue_empty"
54
89
  )
@@ -0,0 +1,68 @@
1
+ from pigeon import BaseMessage
2
+ from typing import Literal, Optional, Tuple
3
+ from pydantic import model_validator, Field
4
+ from . import image_shift
5
+
6
+
7
+ class Command(BaseMessage):
8
+ """
9
+ This message is used to change microscope settings.
10
+ """
11
+
12
+ focus: Optional[int] = Field(
13
+ default=None,
14
+ description="The desired focus value for the microscope, or None to keep the current value.",
15
+ )
16
+ mag_mode: Optional[Literal["LM", "MAG1", "MAG2"]] = Field(
17
+ default=None,
18
+ description="The desired magnification mode for the microscope, or None to keep the current value.",
19
+ )
20
+ mag: Optional[int] = Field(
21
+ default=None,
22
+ description="The desired magnification for the microscope, or None to keep the current value.",
23
+ )
24
+ brightness: Optional[int] = Field(
25
+ default=None,
26
+ description="The desired beam spread for the microscope, or None to keep the current value.",
27
+ )
28
+ beam_offset: Optional[Tuple[int, int]] = Field(
29
+ default=None,
30
+ description="The desired beam shift values, or None to keep the current value.",
31
+ )
32
+ spot_size: Optional[int] = Field(
33
+ default=None,
34
+ description="The desired spot size, or None to keep the current value.",
35
+ )
36
+ screen: Optional[Literal["up", "down"]] = Field(
37
+ default=None,
38
+ description='"up" to raise the microscope viewscreen, "down" to lower the microscope viewscreen, or None to keep the viewscreen in the current position.',
39
+ )
40
+
41
+ @model_validator(mode="after")
42
+ def check_mag(self):
43
+ assert (self.mag_mode is None) == (self.mag is None)
44
+ return self
45
+
46
+
47
+ class Status(BaseMessage):
48
+ """
49
+ This message contains the state of the microscope.
50
+ """
51
+
52
+ focus: int = Field(description="The current focus value.")
53
+ aperture: str | None = Field(
54
+ description="The current status of the objective aperture, or None if unknown."
55
+ )
56
+ mag_mode: Literal["MAG", "LOWMAG"] = Field(
57
+ description="The curreng magnification mode."
58
+ )
59
+ mag: int = Field(description="The current magnification.")
60
+ tank_voltage: int = Field(
61
+ description="The current voltage of the tank in kilo-volts."
62
+ )
63
+ brightness: int = Field(description="The current beam spread.")
64
+ beam_offset: Tuple[int, int] = Field(description="The current beam shift values.")
65
+ spot_size: int = Field(description="The current spot size.")
66
+ screen: Literal["up", "down"] | None = Field(
67
+ description='Whether the viewscreen is currently "up", "down", or None for an unknown position.'
68
+ )
@@ -0,0 +1,57 @@
1
+ from pigeon import BaseMessage
2
+ from typing import Optional, List, Tuple, Mapping
3
+ from pydantic import model_validator, Field
4
+
5
+
6
+ class Calibrate(BaseMessage):
7
+ """
8
+ This message is used to calibrate the image shifting system.
9
+ """
10
+
11
+ position: int = Field(description="The image shift position index to calibrate.")
12
+ image_shift: Tuple[int, int] = Field(
13
+ description="The new calibration values for this position index."
14
+ )
15
+
16
+
17
+ class Status(BaseMessage):
18
+ """
19
+ This message conatins the status of the image shifting system.
20
+ """
21
+
22
+ enabled: bool = Field(
23
+ description="If the image shifting system is currently enabled."
24
+ )
25
+ current_position: int | None = Field(
26
+ default=None, description="The current position index, or None if disabled."
27
+ )
28
+ image_shift: Tuple[int, int] = Field(
29
+ description="The current image shift calibration values."
30
+ )
31
+ calibration: Mapping[int, Tuple[int, int]] = Field(
32
+ description="The calibration values for each position index."
33
+ )
34
+
35
+ @model_validator(mode="after")
36
+ def check_current_position(self):
37
+ assert self.enabled == (self.current_position is not None)
38
+ return self
39
+
40
+
41
+ class Command(BaseMessage):
42
+ """
43
+ This message is used to enable and disable the image shifting system, and to switch to a predefined position.
44
+ """
45
+
46
+ enable: bool = Field(
47
+ description="Used to enable and disable the image shifting system."
48
+ )
49
+ position: Optional[int] = Field(
50
+ default=None,
51
+ description="The calibrated position index to use, or None if disabled.",
52
+ )
53
+
54
+ @model_validator(mode="after")
55
+ def check_position(self):
56
+ assert self.enable == (self.position is not None)
57
+ return self
@@ -1,17 +1,35 @@
1
1
  from pigeon import BaseMessage
2
- from pydantic import ConfigDict
2
+ from pydantic import ConfigDict, Field
3
3
 
4
4
 
5
5
  class Command(BaseMessage):
6
+ """
7
+ This message is uesd to change between apertures on a substrate.
8
+ """
9
+
6
10
  model_config = ConfigDict(extra="allow")
7
11
 
8
- aperture_id: int | None = None
9
- calibrate: bool = False
12
+ aperture_id: int | None = Field(
13
+ default=None,
14
+ description="The ID of the aperture to change to, or None to remain at the current aperture.",
15
+ )
16
+ calibrate: bool = Field(
17
+ default=False,
18
+ description="Set to True to initiate aperture changing hardware calibration.",
19
+ )
10
20
 
11
21
 
12
22
  class Status(BaseMessage):
23
+ """
24
+ This message includes status information about the aperture changing system.
25
+ """
26
+
13
27
  model_config = ConfigDict(extra="allow")
14
28
 
15
- current_aperture: int | None
16
- calibrated: bool
17
- error: str = ""
29
+ current_aperture: int | None = Field(
30
+ description="The ID of the current aperture, or None if unknown."
31
+ )
32
+ calibrated: bool = Field(
33
+ description="True if the aperture changing hardware is calibrated."
34
+ )
35
+ error: str = Field(default="", description="An optional error message.")
TEM_comms/stage/motion.py CHANGED
@@ -1,22 +1,51 @@
1
1
  from typing import Optional
2
2
  from pigeon import BaseMessage
3
- from pydantic import ConfigDict
3
+ from pydantic import ConfigDict, Field
4
4
 
5
5
 
6
6
  class Command(BaseMessage):
7
+ """
8
+ This message is used to specify how the stage should move the sample.
9
+ """
10
+
7
11
  model_config = ConfigDict(extra="allow")
8
12
 
9
- x: int | None = None
10
- y: int | None = None
11
- z: Optional[int] = None
12
- calibrate: bool = False
13
+ x: int | None = Field(
14
+ default=None,
15
+ description="The desired absolute X position of the stage in nanometers, or None to keep the current position.",
16
+ )
17
+ y: int | None = Field(
18
+ default=None,
19
+ description="The desired absolute Y position of the stage in nanometers, or None to keep the current position.",
20
+ )
21
+ z: Optional[int] = Field(
22
+ default=None,
23
+ description="The desired absolute Z position of the stage in nanometers, or None to keep the current position.",
24
+ )
25
+ calibrate: bool = Field(
26
+ default=False, description="Set to True to initiate stage calibration."
27
+ )
13
28
 
14
29
 
15
30
  class Status(BaseMessage):
31
+ """
32
+ This message includes stage motion status information.
33
+ """
34
+
16
35
  model_config = ConfigDict(extra="allow")
17
36
 
18
- x: int | None
19
- y: int | None
20
- z: Optional[int] = None
21
- in_motion: bool
22
- error: str = ""
37
+ x: int | None = Field(
38
+ description="The current X position of the stage in nanometers, or None if unknown."
39
+ )
40
+ y: int | None = Field(
41
+ description="The current Y position of the stage in nanometers, or None if unknown."
42
+ )
43
+ z: Optional[int] = Field(
44
+ default=None,
45
+ description="The current Z position of the stage in nanometers, or None if unknown.",
46
+ )
47
+ in_motion: bool = Field(
48
+ description="This value is True if the stage is currently moving."
49
+ )
50
+ calibrated: bool = Field(description="True if the stage is calibrated.")
51
+ error: str = Field(default="", description="An optional error message.")
@@ -1,16 +1,44 @@
1
1
  from pigeon import BaseMessage
2
+ from pydantic import Field
2
3
 
3
4
 
4
5
  class Command(BaseMessage):
5
- angle_x: float | None = None
6
- angle_y: float | None = None
7
- eucentric_height: float | None = None
8
- calibrate: bool = False
6
+ """
7
+ This message is used to rotate the sample, and to calibrate this rotation.
8
+ """
9
+
10
+ angle_x: float | None = Field(
11
+ default=None,
12
+ description="The desired rotation angle around the X axis in radians, or None to keep the current value.",
13
+ )
14
+ angle_y: float | None = Field(
15
+ default=None,
16
+ description="The desired rotation angle around the Y axis in radians, or None to keep the current value.",
17
+ )
18
+ eucentric_height: float | None = Field(
19
+ default=None,
20
+ description="The eucentric height of the stage in nanometers for calibration, or None to keep the current value.",
21
+ )
22
+ calibrate: bool = Field(
23
+ default=False, description="If True, calibrate the sample rotation hardware."
24
+ )
9
25
 
10
26
 
11
27
  class Status(BaseMessage):
12
- angle_x: float
13
- angle_y: float
14
- eucentric_height: float
15
- in_motion: bool
16
- error: str = ""
28
+ """
29
+ This message contains the status of the sample rotation system.
30
+ """
31
+
32
+ angle_x: float = Field(
33
+ description="The current angle around the X axis in radians."
34
+ )
35
+ angle_y: float = Field(
36
+ description="The current angle around the Y axis in radians."
37
+ )
38
+ eucentric_height: float = Field(
39
+ description="The current enucentric height value in nanometers."
40
+ )
41
+ in_motion: bool = Field(
42
+ description="True if the rotation stage is currently moving."
43
+ )
44
+ error: str = Field(default="", description="An optional error message.")
TEM_comms/state.py ADDED
@@ -0,0 +1,23 @@
1
+ from pigeon import BaseMessage
2
+ from pydantic import Field
3
+
4
+
5
+ class Current(BaseMessage):
6
+ """
7
+ This message contains the current state of the orchastration state machine.
8
+ """
9
+
10
+ state: str = Field(
11
+ description="The current state of the orchastration state machine."
12
+ )
13
+
14
+
15
+ class Change(BaseMessage):
16
+ """
17
+ This message is sent on a state change of the orchastration state machine.
18
+ """
19
+
20
+ old: str = Field(
21
+ description="The previous state of the orchastration state machine."
22
+ )
23
+ new: str = Field(description="The new state of the orchastration state machine.")
@@ -1,42 +1,133 @@
1
- from .metadata import TileMetadata
1
+ from .metadata import TileMetadata, ProcessingOptions
2
2
  from . import statistics
3
- from pydantic import BaseModel
4
- from typing import Literal, List
3
+ from pydantic import BaseModel, Field
4
+ from typing import Literal, List, Tuple, Optional
5
5
 
6
6
 
7
7
  class Preview(TileMetadata):
8
- image: str
8
+ """
9
+ This message contains a downsampled tile image used for UI and calibration purposes.
10
+ """
11
+
12
+ image: str = Field(description="The downsampled tile as a base 64 encoded string.")
9
13
 
10
14
 
11
15
  class Mini(TileMetadata):
12
- image: str
16
+ """
17
+ This message contains a highly downsampled tile image used in the creation of a minimap.
18
+ """
19
+
20
+ image: str = Field(description="The downsampled tile as a base 64 encoded string.")
21
+
13
22
 
23
+ class Raw(TileMetadata, ProcessingOptions):
24
+ """
25
+ This message is sent whenever a new tile is stored on the filesystem and is ready for processing.
26
+ """
14
27
 
15
- class Raw(TileMetadata):
16
- path: str
28
+ path: str = Field(
29
+ description="The path where the raw tile is stored.",
30
+ examples=["/storage/raw/69005602-15b0-4407-bf5b-4bddd6629141.tiff"],
31
+ )
17
32
 
18
33
 
19
34
  class Match(BaseModel):
20
35
  model_config = {"extra": "forbid"}
21
- row: int
22
- column: int
23
- dX: float
24
- dY: float
25
- dXsd: float
26
- dYsd: float
27
- distance: float
28
- rotation: float
29
- match_quality: float
30
- position: Literal["top", "bottom", "left", "right"]
31
- pX: List[int]
32
- pY: List[int]
33
- qX: List[int]
34
- qY: List[int]
36
+
37
+ row: int = Field(description="The row of the neighboring tile.")
38
+ column: int = Field(description="The column of the neighboring tile.")
39
+ dX: float = Field(
40
+ description="The X axis offset of the tile in pixels to get the tile to match."
41
+ )
42
+ dY: float = Field(
43
+ description="The Y axis offset of the tile in pixels to get the tile to match."
44
+ )
45
+ dXsd: float = Field(
46
+ description="The X axis offset standard deviation of all the feature matches."
47
+ )
48
+ dYsd: float = Field(
49
+ description="The Y axis offset standard deviation of all the feature matches."
50
+ )
51
+ distance: float = Field(description="The euclidian offset distance in pixels.")
52
+ rotation: float = Field(
53
+ description="The tile rotation in radians necessary to get the tiles to match."
54
+ )
55
+ position: Literal["top", "bottom", "left", "right"] = Field(
56
+ description="The position of matched tile relative to the captured tile."
57
+ )
58
+ pX: List[float] = Field(description="The captured tile feature X positions.")
59
+ pY: List[float] = Field(description="The captured tile feature Y positions.")
60
+ qX: List[float] = Field(description="The matched tile feature X positions.")
61
+ qY: List[float] = Field(description="The matched tile feature Y positions.")
35
62
 
36
63
 
37
64
  class Matches(TileMetadata):
38
- matches: List[Match]
65
+ """
66
+ This message contains data relating to the matching of neighboring tiles.
67
+ """
68
+
69
+ matches: List[Match] = Field(
70
+ description="Information about how the captured tile matches to each of its available neighbors."
71
+ )
72
+
73
+
74
+ class TemplateMatch(BaseModel):
75
+ model_config = {"extra": "forbid"}
76
+
77
+ offset: Tuple[float, float] = Field(
78
+ description="The offset between the expected and actual template positions."
79
+ )
80
+ distance: float = Field(description="The distance of the offset.")
81
+ rotation: float = Field(description="The angle of the offset.")
82
+ maxVal: float = Field(
83
+ description="The maximum value of the template match."
84
+ )
85
+ minVal: float = Field(
86
+ description="The minimum value of the template match."
87
+ )
88
+ expected_offset_in_crop: Tuple[int, int] = Field(description="")
89
+ maxLoc: Tuple[int, int] = Field(
90
+ description="The maximum location of the template match."
91
+ )
92
+ matched_pos_img2: Tuple[int, int] = Field(
93
+ description="The template top left corner absolute location."
94
+ )
95
+ matched_center_img2: Tuple[int, int] = Field(
96
+ description="The template center absolute location."
97
+ )
98
+ good: bool = Field(description="True if the match is good.")
99
+ reject_reason: Optional[str] = Field(
100
+ default="", description="The reason why the match is not good."
101
+ )
102
+
103
+
104
+ class TemplateMatchContainer(BaseModel):
105
+ model_config = {"extra": "forbid"}
106
+
107
+ row: int = Field(description="The row of the neighboring tile.")
108
+ column: int = Field(description="The column of the neighboring tile.")
109
+ position: Literal["top", "bottom", "left", "right"] = Field(
110
+ description="The position of matched tile relative to the captured tile."
111
+ )
112
+ matches: List[TemplateMatch] = Field(description="A list of data structures containing the information about each individual match.")
113
+
114
+
115
+ class TemplateMatches(TileMetadata):
116
+ """
117
+ This message contains data reltaing to template matches used for calculating the lens correction transform.
118
+ """
119
+
120
+ matches: List[TemplateMatchContainer] = Field(
121
+ description="Information about how the captured tile matches to each of its available neighbors."
122
+ )
39
123
 
40
124
 
41
125
  class Processed(TileMetadata):
42
- path: str
126
+ """
127
+ This message contains the path to a fully processed tile stored on the filesystem.
128
+ """
129
+
130
+ path: str = Field(
131
+ description="The path where the processed tile is stored.",
132
+ examples=["/storage/processed/69005602-15b0-4407-bf5b-4bddd6629141.tiff"],
133
+ )
@@ -1,9 +1,38 @@
1
1
  from pigeon import BaseMessage
2
+ from pydantic import Field
3
+ from typing import Optional
2
4
 
3
5
 
4
6
  class TileMetadata(BaseMessage):
5
- tile_id: str
6
- montage_id: str
7
- row: int
8
- column: int
9
- overlap: int
7
+ tile_id: str = Field(
8
+ description="The tile ID", examples=["69005602-15b0-4407-bf5b-4bddd6629141"]
9
+ )
10
+ montage_id: str = Field(
11
+ description="The montage ID. If a zero length string, the tile is for UI display or calibration purposes only.",
12
+ examples=["4330c7cf-e45b-4950-89cf-82dc0f815fe9"],
13
+ )
14
+ row: int = Field(
15
+ description="The row of the montage where the tile was captured.", examples=[5]
16
+ )
17
+ column: int = Field(
18
+ description="The column of the montage where the tile was captured.",
19
+ examples=[24],
20
+ )
21
+ overlap: int = Field(
22
+ description="The number of pixels of overlap between tiles.", examples=[512]
23
+ )
24
+
25
+
26
+ class ProcessingOptions(BaseMessage):
27
+ brightfield: Optional[bool] = Field(
28
+ default=False,
29
+ description="If true, this tile should be used in creating the brightfield image.",
30
+ )
31
+ darkfield: Optional[bool] = Field(
32
+ default=False,
33
+ description="If true, this tile should be used in creating the darkfield image.",
34
+ )
35
+ lens_correction: Optional[bool] = Field(
36
+ default=False,
37
+ description="If true, this tile is part of a lens correction montage.",
38
+ )
@@ -1,17 +1,34 @@
1
1
  from .metadata import TileMetadata
2
2
  from typing import List
3
+ from pydantic import Field
3
4
 
4
5
 
5
6
  class Focus(TileMetadata):
6
- focus: float
7
+ """
8
+ This message contains the focus score, a measure of the quality of the focus of a given tile.
9
+ """
10
+
11
+ focus: float = Field(
12
+ description="The focus metric. Lower is better", ge=0, le=1, examples=[0.37]
13
+ )
7
14
 
8
15
 
9
16
  class Histogram(TileMetadata):
10
- hist: List[int]
17
+ """
18
+ This message contains raw histogram data for a tile.
19
+ """
20
+
21
+ hist: List[int] = Field(description="The raw histogram data.")
11
22
 
12
23
 
13
24
  class MinMaxMean(TileMetadata):
14
- min: int
15
- max: int
16
- mean: int
17
- std: int
25
+ """
26
+ This message contains simple statistics about a processed tile.
27
+ """
28
+
29
+ min: int = Field(description="The minimum pixel value.", examples=[5])
30
+ max: int = Field(description="The maximum pixel value.", examples=[249])
31
+ mean: int = Field(description="The mean pixel value.", examples=[187])
32
+ std: int = Field(
33
+ description="The standard deviation of the pixel values.", examples=[25]
34
+ )
TEM_comms/ui.py CHANGED
@@ -1,53 +1,82 @@
1
1
  from pigeon import BaseMessage
2
2
  from typing import Optional, Literal
3
- from pydantic import model_validator
3
+ from pydantic import model_validator, Field
4
4
 
5
5
 
6
6
  class Edit(BaseMessage):
7
- roi_id: str
8
- roi_pos_x: int
9
- roi_pox_y: int
10
- roi_width: int
11
- roi_height: int
12
- roi_angle: float
7
+ """
8
+ This message is used to edit existing ROIs in the ROI queue.
9
+ """
10
+
11
+ roi_id: str = Field(description="The ROI id to edit.")
12
+ roi_pos_x: int = Field(description="The new X position of the ROI in nanometers.")
13
+ roi_pox_y: int = Field(description="The new Y position of the ROI in nanometers.")
14
+ roi_width: int = Field(description="The new width of the ROI in nanometers.")
15
+ roi_height: int = Field(description="The new height of the ROI in nanometers.")
16
+ roi_angle: float = Field(description="The new angle to rotate the ROI in radians.")
13
17
 
14
18
 
15
19
  class Run(BaseMessage):
16
- session_id: Optional[str] = None
17
- grid_first: Optional[int] = None
18
- grid_last: Optional[int] = None
19
- montage: bool = False
20
- abort_now: bool = False
21
- abort_at_end: bool = False
22
- resume: bool = False
23
- cancel: bool = False
20
+ """
21
+ This message is used start and stop automated image acquisition.
22
+ """
24
23
 
25
- @model_validator(mode="after")
26
- def check_grid(self):
27
- assert self.montage != (self.grid_first is None)
28
- assert self.montage != (self.grid_last is None)
29
- return self
30
-
31
- @model_validator(mode="after")
32
- def check_session(self):
33
- assert self.montage != (self.session_id is None)
34
- return self
24
+ montage: bool = Field(
25
+ default=False,
26
+ description="Begin collecting montages according to the ROI queue.",
27
+ )
28
+ abort_now: bool = Field(default=False, description="Stop imaging immediately.")
29
+ abort_at_end: bool = Field(
30
+ default=False, description="Stop imaging after the current montage is complete."
31
+ )
32
+ resume: bool = Field(default=False, description="Resume from a failure state.")
33
+ cancel: bool = Field(
34
+ default=False, description="Return to preview mode from a failure state."
35
+ )
35
36
 
36
37
 
37
38
  class Setup(BaseMessage):
38
- conch_owner: Optional[str] = None
39
- auto_focus: bool = False
40
- auto_exposure: bool = False
41
- lens_correction: bool = False
42
- acquire_brightfield: bool = False
43
- acquire_darkfield: bool = False
44
- center_beam: bool = False
45
- spread_beam: bool = False
46
- find_aperture: bool = False
47
- calibrate_resolution: bool = False
48
- grid: Optional[int] = None
49
- mag_mode: Optional[Literal["LM", "MAG1", "MAG2"]] = None
50
- mag: Optional[int] = None
39
+ """
40
+ This message is utilized to setup a microscope in a semi-automated manner. Each of the fields in this message instructs the system to run an individual setup routine.
41
+ """
42
+
43
+ auto_focus: bool = Field(
44
+ default=False, description="Automatically focus the microscope."
45
+ )
46
+ auto_exposure: bool = Field(
47
+ default=False, description="Optimize the camera exposure."
48
+ )
49
+ lens_correction: bool = Field(
50
+ default=False, description="Collect and generate a lens correction."
51
+ )
52
+ acquire_brightfield: bool = Field(
53
+ default=False, description="Acquire a darkfield image."
54
+ )
55
+ acquire_darkfield: bool = Field(
56
+ default=False, description="Acquire a brightfield image."
57
+ )
58
+ center_beam: bool = Field(
59
+ default=False, description="Center the beam in the image frame."
60
+ )
61
+ spread_beam: bool = Field(default=False, description="Spread the beam.")
62
+ find_aperture: bool = Field(
63
+ default=False, description="Find and move to the aperture centroid."
64
+ )
65
+ calibrate_resolution: bool = Field(
66
+ default=False,
67
+ description="Calibrate the resolution of the microscope at the current mag level.",
68
+ )
69
+ grid: Optional[int] = Field(
70
+ default=None, description="Change to the specified grid."
71
+ )
72
+ mag_mode: Optional[Literal["LM", "MAG1", "MAG2"]] = Field(
73
+ default=None,
74
+ description='Change to the specified mag mode. The "mag" field must also be specified.',
75
+ )
76
+ mag: Optional[int] = Field(
77
+ default=None,
78
+ description='Change to the specified magnification. "mga_mode" must also be specified.',
79
+ )
51
80
 
52
81
  @model_validator(mode="after")
53
82
  def check_mag(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pigeon-tem-comms
3
- Version: 1.4.1
3
+ Version: 2.1.0
4
4
  Summary: Pigeon messages for the next generation TEM imaging system.
5
5
  Author-email: Cameron Devine <cameron.devine@alleninstitute.org>
6
6
  License: BSD 3 Clause
@@ -0,0 +1,25 @@
1
+ TEM_comms/__init__.py,sha256=c2UGXOvlwg_Xy5WTQBaIS2ebmgEtJIbIwyoh7AzDjDk,1850
2
+ TEM_comms/buffer.py,sha256=bQiYDQDOMDBEfx6QKWFKUJE14z-xn8SqwnqE1LqL5Io,546
3
+ TEM_comms/calibration.py,sha256=Nd5u1HA5qwA_04pOeyklVLZkdV06e9y0kS2Gv9qvJNg,1383
4
+ TEM_comms/camera.py,sha256=XrRVU-4hAGHJEbfvoj3b7359NEuTuRyqtMLL9e0V3xI,2164
5
+ TEM_comms/lens_correction.py,sha256=KCsI1jMuPwHTxfHzx4_MP7VXdgcq6DpESQOYXV4NVqQ,290
6
+ TEM_comms/montage.py,sha256=GevXbNdf8NGmvxR-2RPEK4JeSGtNdTXpNE_I2PSjTZ8,2157
7
+ TEM_comms/qc.py,sha256=sFUTBjroGZrKjuL9u0LO6DT9mn10IEQsfuMv1QjgiLU,627
8
+ TEM_comms/roi.py,sha256=dxtpPvfBGyIwGnhSI7x9F-qa8dASHV4Qt1I8DKIYNZc,3383
9
+ TEM_comms/state.py,sha256=QGHAGtVHqbURU3qpJkuaKl8DDtuNkOqG4ubgNcQDvrg,607
10
+ TEM_comms/ui.py,sha256=ZMRth7VKbeIlsIpZzc6vVYG3GC1ArIkxAierGNzTEDs,3185
11
+ TEM_comms/scope/__init__.py,sha256=S33FOns1MFRD0AZ2EO-ZaDUW8cDbvpKNi_18qDS7fms,2597
12
+ TEM_comms/scope/image_shift.py,sha256=kMx32RqUrCv2KvVYxQDqZ8LjVGJlhcu8DzmcCW11i44,1766
13
+ TEM_comms/stage/__init__.py,sha256=UVtYKzMBI01FUADS24xSItNIjhqdZtlDeJdqsDJNVIc,67
14
+ TEM_comms/stage/aperture.py,sha256=RoZjwmUMsgZcFTW6OYasK7g9pbz5hE0fUAOvWqVPZeA,1029
15
+ TEM_comms/stage/motion.py,sha256=Ox_WIdhKxhkr7Q9QxbdKRKx-84az5tT_L1yUlxxNrXo,1730
16
+ TEM_comms/stage/rotation.py,sha256=_rMk9ES7_ivMZ1AypqFVdvcuTbTQ32g9La2L1ZLdMTE,1476
17
+ TEM_comms/tile/__init__.py,sha256=_SN7yS_rbosNsZ0n9M62WOlD_WSHnjaLoeg-vvdDeEY,4895
18
+ TEM_comms/tile/metadata.py,sha256=iLKEmi-ocC0I_LU4YVk0AymL5zxSmmquk-fKDiKyUg0,1332
19
+ TEM_comms/tile/statistics.py,sha256=hinAROuYdL4l0SNAdvndqetbWhDiF1ANjMoLJoPubpM,976
20
+ pigeon_tem_comms-2.1.0.dist-info/licenses/LICENSE,sha256=tZfQIUX7uNGacTb751ZWInVenJl6OwYQihcGbYrKSts,1463
21
+ pigeon_tem_comms-2.1.0.dist-info/METADATA,sha256=X_1JfzwWjSSCANDiSPyyNSASCEoVKIMn154Gq4aZY24,1575
22
+ pigeon_tem_comms-2.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ pigeon_tem_comms-2.1.0.dist-info/entry_points.txt,sha256=X2WfBCDgfH9XVVqiI4rLu7BLyMNuLW6WWckqXxfbrgk,38
24
+ pigeon_tem_comms-2.1.0.dist-info/top_level.txt,sha256=3_1RcQ7xvS2dJEYnPIigo9snoKcSXSNJzPqunY0qydI,10
25
+ pigeon_tem_comms-2.1.0.dist-info/RECORD,,
TEM_comms/scope.py DELETED
@@ -1,30 +0,0 @@
1
- from pigeon import BaseMessage
2
- from typing import Literal, Optional, Tuple
3
- from pydantic import model_validator
4
-
5
-
6
- class Command(BaseMessage):
7
- focus: Optional[int] = None
8
- mag_mode: Optional[Literal["LM", "MAG1", "MAG2"]] = None
9
- mag: Optional[int] = None
10
- brightness: Optional[int] = None
11
- beam_offset: Optional[Tuple[int, int]] = None
12
- spot_size: Optional[int] = None
13
- screen: Optional[Literal["up", "down"]] = None
14
-
15
- @model_validator(mode="after")
16
- def check_mag(self):
17
- assert (self.mag_mode is None) == (self.mag is None)
18
- return self
19
-
20
-
21
- class Status(BaseMessage):
22
- focus: int
23
- aperture: str | None
24
- mag_mode: Literal["MAG", "LOWMAG"]
25
- mag: int
26
- tank_voltage: int
27
- brightness: int
28
- beam_offset: Tuple[int, int]
29
- spot_size: int
30
- screen: Literal["up", "down"] | None
@@ -1,22 +0,0 @@
1
- TEM_comms/__init__.py,sha256=sXY1A8yg0ihaT0Y7kSUXiNp35ibQxVJ4EjIgNoCQKOc,1504
2
- TEM_comms/buffer.py,sha256=Jr6fUIbUPreQSSGKWR7jkA1McyZZv3lVE8uQSER8VL4,123
3
- TEM_comms/calibration.py,sha256=aDxJ0VepU9A6dZR-SK1yEoTFLwzF6bz5eingSMJLcvw,327
4
- TEM_comms/camera.py,sha256=5tOFqm7zOPVoX6ZFr5KIMxb0jAjWpE8yo8bctvOpbcU,698
5
- TEM_comms/montage.py,sha256=QQ5IJcIfPb4y5QdZ-lXYNV0WLTcZUrLmOwuS0t84_2M,666
6
- TEM_comms/qc.py,sha256=sujy2i0wimMvrHDPEXKdaiO7xxvRcHiy83mVpATLyqA,141
7
- TEM_comms/roi.py,sha256=bKPBDNi1i1jxROtiw5bGh4Zn3-E0-LW_DWazYdg4PLs,2065
8
- TEM_comms/scope.py,sha256=7fTqhgfaNCGYsd66vCx_zTb5dW442s8cWi3RXmLNyG8,837
9
- TEM_comms/ui.py,sha256=pyjF9vex2qVzmKCoHdVTtDMEKRDOuhO-dWTBa2qb47Y,1484
10
- TEM_comms/stage/__init__.py,sha256=UVtYKzMBI01FUADS24xSItNIjhqdZtlDeJdqsDJNVIc,67
11
- TEM_comms/stage/aperture.py,sha256=QBO9MiVgzWJM_oAdy-9-z2qtX-pUqLyYSuMXkIDwMX4,351
12
- TEM_comms/stage/motion.py,sha256=EiMDAviHZo8DYjzeFcYFkbHFs90kLDsZKbrcla0y7eU,452
13
- TEM_comms/stage/rotation.py,sha256=tUcf6IpQ2ooRDYb51Zb4pi7pQUrBuLPiLKSg8Ebb2DY,332
14
- TEM_comms/tile/__init__.py,sha256=nLWbdyLdI1qkIYmnVDKt4PAu29e2VQuXqcWwWXczdgE,704
15
- TEM_comms/tile/metadata.py,sha256=pUxmi_C02e5nwTm51rIULkKaRz6ij5mlXylzAcDU-9c,149
16
- TEM_comms/tile/statistics.py,sha256=C1pMEOjwVrhjGR6R7efKJE-pR_tw9ZAGrprLIHcXc_A,245
17
- pigeon_tem_comms-1.4.1.dist-info/licenses/LICENSE,sha256=tZfQIUX7uNGacTb751ZWInVenJl6OwYQihcGbYrKSts,1463
18
- pigeon_tem_comms-1.4.1.dist-info/METADATA,sha256=5ApKdnJVbS7WxgCHjVRsgWiysPwih9-Obe3iQw9szVI,1575
19
- pigeon_tem_comms-1.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- pigeon_tem_comms-1.4.1.dist-info/entry_points.txt,sha256=X2WfBCDgfH9XVVqiI4rLu7BLyMNuLW6WWckqXxfbrgk,38
21
- pigeon_tem_comms-1.4.1.dist-info/top_level.txt,sha256=3_1RcQ7xvS2dJEYnPIigo9snoKcSXSNJzPqunY0qydI,10
22
- pigeon_tem_comms-1.4.1.dist-info/RECORD,,