Skip to content

Commit

Permalink
Merge branch 'devel'
Browse files Browse the repository at this point in the history
  • Loading branch information
maxcorsini committed Nov 22, 2024
2 parents ae2c897 + 9514cdd commit 4dead9b
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 6 deletions.
143 changes: 141 additions & 2 deletions TagLab.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@
from source.QtDictionaryWidget import QtDictionaryWidget
from source.QtRegionAttributesWidget import QtRegionAttributesWidget
from source.QtShapefileAttributeWidget import QtAttributeWidget

# from source.QtDXFfileAttributeWidget import QtDXFExportWidget
import ezdxf
from ezdxf.enums import TextEntityAlignment
from ezdxf.entities import Layer
import math

from source.QtPanelInfo import QtPanelInfo
from source.Sampler import Sampler

Expand Down Expand Up @@ -1056,8 +1063,11 @@ def createMenuBar(self):
exportShapefilesAct.setStatusTip("Export visible regions as shapefile")
exportShapefilesAct.triggered.connect(self.exportAnnAsShapefiles)

exportDXFfilesAct = QAction("Export Regions As DXF", self)
exportDXFfilesAct.setStatusTip("Export visible regions as DXF")
exportDXFfilesAct.triggered.connect(self.exportAnnAsDXF)

exportGeoRefLabelMapAct = QAction("Export Regions As A GeoTiff", self)
# exportShapefilesAct.setShortcut('Ctrl+??')
exportGeoRefLabelMapAct.setStatusTip("Create a label image and export it as a GeoTiff")
exportGeoRefLabelMapAct.triggered.connect(self.exportGeoRefLabelMap)

Expand Down Expand Up @@ -1142,6 +1152,7 @@ def createMenuBar(self):
self.submenuExport.addAction(exportDataTableAct)
self.submenuExport.addAction(exportMapAct)
self.submenuExport.addAction(exportShapefilesAct)
self.submenuExport.addAction(exportDXFfilesAct)
self.submenuExport.addAction(exportGeoRefLabelMapAct)
self.submenuExport.addAction(exportGeoRefImgAct)
self.submenuExport.addAction(exportHistogramAct)
Expand Down Expand Up @@ -4711,7 +4722,7 @@ def exportAnnAsShapefiles(self):
if self.activeviewer.image is not None:
if self.activeviewer.image.georef_filename == "":
box = QMessageBox()
box.setText("Georeference information are not available.")
box.setText("Georeferencing is not available.")
box.exec()
return

Expand All @@ -4729,6 +4740,134 @@ def exportAnnAsShapefiles(self):
msgBox.exec()
return

@pyqtSlot()
def exportAnnAsDXF(self):
# Check if activeviewer is set and contains necessary data
if self.activeviewer is None:
return

if self.activeviewer.image is not None:
# Open a file dialog to select the output file
filters = "DXF (*.dxf)"
output_filename, _ = QFileDialog.getSaveFileName(self, "Save DXF File As", self.taglab_dir, filters)

if output_filename:

# Create a new DXF document
doc = ezdxf.new()
msp = doc.modelspace()

try:
# Check if georeferencing information is available and ask the user if wants to use it
georef = None
text_height_scale = 1.0
if hasattr(self.activeviewer.image, 'georef_filename') and self.activeviewer.image.georef_filename:
reply = QMessageBox.question(self, "Georeference Information",
"Georeference information is available. Do you want to use it?",
QMessageBox.Yes | QMessageBox.No)
if reply == QMessageBox.Yes:
georef, transform = rasterops.load_georef(self.activeviewer.image.georef_filename)
text_height_scale = max(abs(transform.a), abs(transform.e))

if self.project.working_area is None:
# Get blobs from the activeviewer
blobs = self.activeviewer.annotations.seg_blobs
# Add the outline of the map to layer 0
map_outline = [
(0, 0),
(self.activeviewer.image.width, 0),
(self.activeviewer.image.width, self.activeviewer.image.height),
(0, self.activeviewer.image.height),
(0, 0)
]
else:
# Get blobs inside the working area
blobs = self.activeviewer.annotations.calculate_inner_blobs(self.project.working_area)
# Add the outline of the working area to layer 0
map_outline = [
(self.project.working_area[1], self.project.working_area[0]),
(self.project.working_area[1] + self.project.working_area[2], self.project.working_area[0]),
(self.project.working_area[1] + self.project.working_area[2], self.project.working_area[0] + self.project.working_area[3]),
(self.project.working_area[1], self.project.working_area[0] + self.project.working_area[3]),
(self.project.working_area[1], self.project.working_area[0])
]

if georef:
map_outline = [transform * (x, y) for x, y in map_outline]
msp.add_lwpolyline(
map_outline,
close=True,
dxfattribs={'layer': '0'}
)

# Add points to the DXF file from 'blobs' data
for blob in blobs:
# Set each class as a new layer
layer_name = blob.class_name

# Set color for the layer from blob class color
col = self.project.labels[blob.class_name].fill

# Convert the color to a DXF True color code
color_code = ezdxf.colors.rgb2int(col)

if not doc.layers.has_entry(layer_name):
doc.layers.new(name=layer_name, dxfattribs={'true_color': color_code})

# Add the outer contour
if georef:
points = [transform * (x, y) for x, y in blob.contour]
else:
points = [(x, y) for x, y in blob.contour]
if points:
msp.add_lwpolyline(
points,
close=True,
dxfattribs={'layer': layer_name}
)

# Add inner contours (holes)
for inner_contour in blob.inner_contours:
# inner_points = transform_coords([(x, y) for x, y in inner_contour])
if georef:
inner_points = [transform * (x, y) for x, y in inner_contour]
else:
inner_points = [(x, y) for x, y in inner_contour]
if inner_points:
msp.add_lwpolyline(inner_points, close=True, dxfattribs={'layer': layer_name})

# Add the class_name as a text annotation at the blob's centroid
if blob.class_name:
x, y = blob.centroid
if georef:
x, y = transform * (x, y)
msp.add_text(
blob.class_name, height=text_height_scale * 22.0,
dxfattribs={
'layer': layer_name
}
).set_placement((x, y), align=TextEntityAlignment.MIDDLE_CENTER)

# Save the DXF file
doc.saveas(output_filename)

# Show a confirmation message box
msgBox = QMessageBox(self)
msgBox.setWindowTitle("Export Successful")
msgBox.setText("DXF file exported successfully!")
msgBox.exec()
return
except Exception as e:
msgBox = QMessageBox(self)
msgBox.setWindowTitle("Export Failed")
if "/" in str(e):
print("/ inside a class, please rename the class before continuing")
msgBox.setText("Error exporting DXF file:\nforbidden character (/) inside class names, please rename the classes before continuing")
else:
msgBox.setText("Error exporting DXF file: " + str(e))
msgBox.exec()
return

@pyqtSlot()
def exportGeoRefLabelMap(self):

Expand Down
1 change: 1 addition & 0 deletions install.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@
'shapely',
'pycocotools',
'qhoptim',
'ezdxf',

# CoralNet Toolbox
'Requests',
Expand Down
8 changes: 4 additions & 4 deletions source/Annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,16 +814,16 @@ def export_data_table(self, project, image, imagename, filename, choice):

dict = {
'Image name': [],
'TagLab Id': np.zeros(number_of_rows, dtype=np.int64),
'TagLab Type': [],
'TagLab Date': [],
'TagLab Class name': [],
'TagLab Type': [],
'TagLab Genet Id': np.zeros(number_of_rows, dtype=np.int64),
'TagLab Id': np.zeros(number_of_rows, dtype=np.int64),
'TagLab Class name': [],
'TagLab Centroid x': np.zeros(number_of_rows),
'TagLab Centroid y': np.zeros(number_of_rows),
'TagLab Perimeter': np.zeros(number_of_rows),
'TagLab Area': np.zeros(number_of_rows),
'TagLab Surf. area': np.zeros(number_of_rows),
'TagLab Perimeter': np.zeros(number_of_rows),
'TagLab Note': []}

# Are attributes named the same? Check
Expand Down
6 changes: 6 additions & 0 deletions source/RasterOps.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,12 @@ def write_shapefile( project, image, blobs, georef_filename, out_shp):
# Save and close everything
ds = layer = feat = geom = None

def load_georef(image):
# load georeference information to use
img = rio.open(image)
geoinfo = img.crs
transform = img.transform
return geoinfo, transform

def saveClippedTiff(input, blobs, georef_filename, name):

Expand Down
31 changes: 31 additions & 0 deletions source/genutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

from source.Mask import checkIntersection, intersectMask

import math

def clampCoords(x, y, W, H):

if x < 0:
Expand Down Expand Up @@ -542,3 +544,32 @@ def qimageToNumpyArray(qimg):
arr[:, :, 2] = arrtemp[:, :, 0]

return arr

# ACI (Autocad Color Index) for DXF export
def rgb_to_aci(self, r, g, b):
# Define the simplified ACI color palette
aci_palette = [
(0, 0, 0), # Color 0: Black
(255, 0, 0), # Color 1: Red
(255, 255, 0), # Color 2: Yellow
(0, 255, 0), # Color 3: Green
(0, 255, 255), # Color 4: Cyan
(0, 0, 255), # Color 5: Blue
(255, 0, 255), # Color 6: Magenta
(255, 255, 255) # Color 7: White
]
aci_palette_complete =[
(0, 0, 0), (255, 0, 0), (255, 255, 0), (0, 255, 0), (0, 255, 255), (0, 0, 255), (255, 0, 255), (255, 255, 255), (128, 128, 128), (192, 192, 192), (255, 0, 0), (255, 25, 0), (255, 50, 0), (255, 75, 0), (255, 100, 0), (255, 125, 0), (255, 150, 0), (255, 175, 0), (255, 200, 0), (255, 225, 0), (255, 255, 0), (230, 255, 0), (205, 255, 0), (180, 255, 0), (155, 255, 0), (130, 255, 0), (105, 255, 0), (80, 255, 0), (55, 255, 0), (30, 255, 0), (0, 255, 0), (0, 255, 25), (0, 255, 50), (0, 255, 75), (0, 255, 100), (0, 255, 125), (0, 255, 150), (0, 255, 175), (0, 255, 200), (0, 255, 225), (0, 255, 255), (0, 247, 255), (0, 239, 255), (0, 231, 255), (0, 223, 255), (0, 215, 255), (0, 207, 255), (0, 199, 255), (0, 191, 255), (0, 183, 255), (0, 175, 255), (0, 167, 255), (0, 159, 255), (0, 151, 255), (0, 143, 255), (0, 135, 255), (0, 127, 255), (0, 119, 255), (0, 111, 255), (0, 103, 255), (0, 95, 255), (0, 87, 255), (0, 79, 255), (0, 71, 255), (0, 63, 255), (0, 55, 255), (0, 47, 255), (0, 39, 255), (0, 31, 255), (0, 23, 255), (0, 0, 255), (8, 0, 255), (16, 0, 255), (24, 0, 255), (32, 0, 255), (40, 0, 255), (48, 0, 255), (56, 0, 255), (64, 0, 255), (72, 0, 255), (80, 0, 255), (88, 0, 255), (96, 0, 255), (104, 0, 255), (112, 0, 255), (120, 0, 255), (128, 0, 255), (136, 0, 255), (144, 0, 255), (152, 0, 255), (160, 0, 255), (168, 0, 255), (176, 0, 255), (184, 0, 255), (192, 0, 255), (200, 0, 255), (208, 0, 255), (216, 0, 255), (224, 0, 255), (232, 0, 255), (255, 0, 255), (255, 0, 247), (255, 0, 239), (255, 0, 231), (255, 0, 223), (255, 0, 215), (255, 0, 207), (255, 0, 199), (255, 0, 191), (255, 0, 183), (255, 0, 175), (255, 0, 167), (255, 0, 159), (255, 0, 151), (255, 0, 143), (255, 0, 135), (255, 0, 127), (255, 0, 119), (255, 0, 111), (255, 0, 103), (255, 0, 95), (255, 0, 87), (255, 0, 79), (255, 0, 71), (255, 0, 63), (255, 0, 55), (255, 0, 47), (255, 0, 39), (255, 0, 31), (255, 0, 23), (255, 0, 255), (247, 8, 255), (239, 16, 255), (231, 24, 255), (223, 32, 255), (215, 40, 255), (207, 48, 255), (199, 56, 255), (191, 64, 255), (183, 72, 255), (175, 80, 255), (167, 88, 255), (159, 96, 255), (151, 104, 255), (143, 112, 255), (135, 120, 255), (127, 128, 255), (119, 136, 255), (111, 144, 255), (103, 152, 255), (95, 160, 255), (87, 168, 255), (79, 176, 255), (71, 184, 255), (63, 192, 255), (55, 200, 255), (47, 208, 255), (39, 216, 255), (31, 224, 255), (23, 232, 255), (255, 255, 255), (255, 247, 247), (255, 239, 239), (255, 231, 231), (255, 223, 223), (255, 215, 215), (255, 207, 207), (255, 199, 199), (255, 191, 191), (255, 183, 183), (255, 175, 175), (255, 167, 167), (255, 159, 159), (255, 151, 151), (255, 143, 143), (255, 135, 135), (255, 127, 127), (255, 119, 119), (255, 111, 111), (255, 103, 103), (255, 95, 95), (255, 87, 87), (255, 79, 79), (255, 71, 71), (255, 63, 63), (255, 55, 55), (255, 47, 47), (255, 39, 39), (255, 31, 31), (255, 23, 23), (0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5), (6, 6, 6), (7, 7, 7), (8, 8, 8), (9, 9, 9), (10, 10, 10), (11, 11, 11), (12, 12, 12), (13, 13, 13), (14, 14, 14), (15, 15, 15), (16, 16, 16), (17, 17, 17), (18, 18, 18), (19, 19, 19), (20, 20, 20), (21, 21, 21), (22, 22, 22), (23, 23, 23), (24, 24, 24), (25, 25, 25), (26, 26, 26), (27, 27, 27), (28, 28, 28), (29, 29, 29), (255, 255, 255), (244, 244, 244), (233, 233, 233), (222, 222, 222), (211, 211, 211), (200, 200, 200), (189, 189, 189), (178, 178, 178), (167, 167, 167), (156, 156, 156), (145, 145, 145), (134, 134, 134), (123, 123, 123), (112, 112, 112), (101, 101, 101), (90, 90, 90), (79, 79, 79), (68, 68, 68), (57, 57, 57), (46, 46, 46), (35, 35, 35), (24, 24, 24), (13, 13, 13), (2, 2, 2), (-9, -9, -9), (-20, -20, -20), (-31, -31, -31), (-42, -42, -42), (-53, -53, -53), (-64, -64, -64), (255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (0, 255, 255), (255, 0, 255)
]

"""Convert an RGB color to the closest ACI color."""
min_distance = float('inf')
closest_aci = 0

for aci, (r_aci, g_aci, b_aci) in enumerate(aci_palette):
distance = math.sqrt((r - r_aci) ** 2 + (g - g_aci) ** 2 + (b - b_aci) ** 2)
if distance < min_distance:
min_distance = distance
closest_aci = aci

return closest_aci

0 comments on commit 4dead9b

Please sign in to comment.