Skip to content

Commit 3da398e

Browse files
feat(export): export traces as ImageJ .roi files
1 parent ae0e9fc commit 3da398e

File tree

7 files changed

+93
-15
lines changed

7 files changed

+93
-15
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""ImageJ .roi file exporter."""
2+
3+
from pathlib import Path
4+
from typing import Union, List, Tuple
5+
6+
from roifile import ImagejRoi, ROI_TYPE
7+
8+
from PyReconstruct.modules.datatypes import Trace
9+
10+
11+
coordinates = List[Tuple[float, float]]
12+
filepath = Union[str, Path]
13+
14+
15+
class RoiExporter:
16+
17+
def __init__(self, trace: Trace, mag: float, img_height: int):
18+
19+
self.trace = trace
20+
self.coords = self.get_coords(mag, img_height)
21+
self.roi = self.get_roi()
22+
23+
def export_roi(self, directory: filepath) -> None:
24+
"""Export an ImageJ .roi file to a directory."""
25+
26+
if not isinstance(directory, Path):
27+
directory = Path(directory)
28+
29+
## Assume each trace uniquely named for now
30+
output_fp = directory / f"{self.trace.name}-exported.roi"
31+
32+
self.roi.tofile(output_fp)
33+
34+
return None
35+
36+
def get_roi(self) -> ImagejRoi:
37+
"""Get an ImageJ roi object."""
38+
39+
roi = ImagejRoi.frompoints(self.coords)
40+
41+
roi.roitype = ROI_TYPE.POLYGON if self.trace.closed else ROI_TYPE.FREEHAND
42+
roi.name = self.trace.name
43+
44+
return roi
45+
46+
def get_coords(self, mag, img_height) -> coordinates:
47+
"""Get coordinates as pixels."""
48+
49+
coords = self.trace.asPixels(mag, img_height, subpix=True)
50+
51+
return [
52+
(round(x, 3), round(y, 3)) for x, y in coords
53+
]
54+

PyReconstruct/modules/backend/imports/imagej_roi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def __init__(self, roi_fp):
2424
def trace_closed_p(self) -> bool:
2525
"""Return true if trace closed else false."""
2626

27-
roi_closed_types = [0, 1, 2, 3, 9, 10]
27+
roi_closed_types = [0, 1, 2, 3, 7, 9, 10]
2828

2929
if self.roi.roitype in roi_closed_types:
3030
return True

PyReconstruct/modules/calc/image.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,33 @@ def getImgDims(img_fp: Union[str, Path]) -> Tuple[height, width]:
2424
return img.shape
2525

2626

27-
def point_2_pix(coordinate: Sequence[float], mag: float, height: height) -> Tuple[int, int]:
27+
def point_2_pix(
28+
coordinate: Sequence[float],
29+
mag: float, height: height,
30+
subpix: bool=False
31+
) -> Union[Tuple[float, float], Tuple[int, int]]:
2832
"""Convert a single point to pixels."""
2933

30-
x = int(coordinate[0] // mag)
31-
y = int(height - (coordinate[1] // mag))
34+
x = coordinate[0] / mag
35+
y = height - (coordinate[1] / mag)
36+
37+
if not subpix:
38+
39+
x = int(x)
40+
y = int(y)
3241

3342
return x, y
3443

3544

3645
def point_list_2_pix(
3746
points: List[Tuple[int, int]],
3847
mag: float,
39-
height: int) -> List[Tuple[int, int]]:
48+
height: int,
49+
subpix: bool=False
50+
) -> List[Tuple[int, int]]:
4051
"""Convert a points list to pixels."""
4152

42-
mapped_points = map(lambda x: point_2_pix(x, mag, height), points)
53+
mapped_points = map(lambda x: point_2_pix(x, mag, height, subpix), points)
4354
return list(mapped_points)
4455

4556

PyReconstruct/modules/datatypes/trace.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ def add(self, point : tuple):
7575
"""
7676
self.points.append(point)
7777

78-
def asPixels(self, mag: float, img_height: int):
78+
def asPixels(self, mag: float, img_height: int, subpix: bool=False):
7979
"""Return points as a list of (x, y) pixels on an image."""
8080

81-
return point_list_2_pix(self.points, mag, img_height)
81+
return point_list_2_pix(self.points, mag, img_height, subpix)
8282

8383
def isSameTrace(self, other) -> bool:
8484
"""Check if traces have the same name, color, and points.
@@ -568,7 +568,7 @@ def smooth(self, window: int, spacing: Union[int, float]) -> None:
568568
smoothed = smoothed[:-1]
569569

570570
self.points = smoothed
571-
571+
572572
@staticmethod
573573
def get_scale_bar():
574574
"""Return a scale bar trace object."""

PyReconstruct/modules/gui/main/main_imports.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@
116116
Roi
117117
)
118118

119+
from PyReconstruct.modules.backend.exports.roi_export import RoiExporter
120+
119121
from PyReconstruct.modules.backend.remote import (
120122
download_vol_as_tifs
121123
)

PyReconstruct/modules/gui/main/main_window.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,11 +1180,22 @@ def exportSectionPNG(self):
11801180
def exportROIFiles(self):
11811181
"""Export traces as ImageJ .roi files."""
11821182

1183-
#notify("Traces exported as .roi files.")
1183+
directory = FileDialog.get("dir", self, "Select directory to export .roi files.")
11841184

1185-
notify("This feature is still being implemented! Stay tuned!")
1185+
if not directory:
1186+
return
1187+
1188+
h, _ = self.field.section.img_dims
1189+
mag = self.field.section.mag
1190+
1191+
contours = self.field.section.contours
1192+
1193+
for _, contour in contours.items():
1194+
for trace in contour.traces:
1195+
exporter = RoiExporter(trace, mag, h)
1196+
exporter.export_roi(directory)
11861197

1187-
pass
1198+
notify("Traces exported as .roi files.")
11881199

11891200
def downloadExample(self):
11901201
"""Download example kharris2015 images to local machine."""

PyReconstruct/modules/gui/main/menubar.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,9 @@ def return_section_menu(self):
225225
"text": "Export",
226226
"opts":
227227
[
228-
("exportsvg_act", "As svg...", "", self.exportSectionSVG),
229-
("exportpng_act", "As png...", "", self.exportSectionPNG),
230-
("exportroi_act", "As png...", "", self.exportROIFiles)
228+
("exportsvg_act", "As .svg...", "", self.exportSectionSVG),
229+
("exportpng_act", "As .png...", "", self.exportSectionPNG),
230+
("exportroi_act", "As .roi...", "", self.exportROIFiles)
231231
]
232232
}
233233
]

0 commit comments

Comments
 (0)