Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image outline #578

Open
wants to merge 42 commits into
base: master
Choose a base branch
from
Open

Image outline #578

wants to merge 42 commits into from

Conversation

Rdornier
Copy link
Contributor

Hello,

This PR implements the feature from #499.
Almost everything is working properly. I only have one small detail that I couldn't fix. The outline rectangle, even if it copies the viewport coordinates, seem to not be centered on the image. I wasn't able to find a solution yet. Still thinking about it...

image

@will-moore
Copy link
Member

This is tricky to get right with precise pixel coordinates.

Maybe try instead to add a css border to this.$panel_canvas in the panel_view.js?

@Rdornier
Copy link
Contributor Author

Rdornier commented Aug 8, 2024

If we stick with the current behavior, I would rather keep the ROI creation than moving to css border. It allows the user to easily delete the border like any other ROIs.

But... as the PR #549 will create some sync insets, I think it is better to integrate this PR to the Inset Feature one and to create a css border (not a ROI anymore) with the same color as the sync rectangle (the strock width can be hardcoded). Having the colored border will help visualizing the "filiation".

The reason I opened this PR was to be able to visually know which zoomed image was linked to which rectangle on the main image. So, if we managed to merge both PRs, it will be very cool !

@will-moore will-moore mentioned this pull request Aug 9, 2024
@will-moore
Copy link
Member

I'm thinking of adding a Border section the Labels tab, with just a colour-picker and stroke-width picker and show/hide button (like the scalebar but simpler).
This is a bit more work, but it will allow you to add, edit & show/hide a border for multiple selected panels at once, and it will be more obvious than adding this under the Edit ROIs dialog.

@Rdornier
Copy link
Contributor Author

Ok, now I better catch your point. Indeed, it will be easier to include this feature in the right panel.
I'll refactor the code in that direction.
Thanks.

@will-moore
Copy link
Member

I tried experimenting in the browser dev tools to add borders around image panels (see screenshots on #549).

I guess we need to decide if the border should go inside or outside the current boundary of the panel?
E.g. when you add a border, does it go around the outside of the panel (and reduce the gap between panels) or inside the panel and reduce the image area?
I can see arguments for both, but I think going inside is slightly easier because going outside means that we also need to update the x/y/width/height coordinates of the panel on the page, since it will take up more space. That's maybe not too hard but just something to think about...

@Rdornier
Copy link
Contributor Author

Hi,

I've modified the code to create a CSS border, as discussed above. The border is set inside the panel, so no need to change panel coordinates.
However, when I apply the border, the image is shifted to the bottom right corner. I couldn't find a way to remove this shift.

OMERO figure_outline_shift

I also have the same issue with the strokewidth drop-down menu that doesn't display well and prevents selecting more than one width.

@will-moore
Copy link
Member

A couple of points:
I wonder if we could use the term "border" instead of "outline" everywhere (code and UI)? Maybe I'm just thinking css but to me this feature is a border.

Can you toggle the Show/Hide in the same way that the Scalebar does? I don't think we need a separate Remove button.

I think we need the border to be outside the regular x,y,w,h rectangle, so that it doesn't affect the image area shown.
This means we need to tweak the actual x,y,w,h of the panel on the page.
Your panel show_outline() wasn't being called when the panel initially renders (only when the outline changes) so when you re-load the figure you don't see it.

E.g. duplicated panel, aligned to top:

Screenshot 2024-08-28 at 17 02 32

I updated the render_layout() etc... (not looked at the form drop-down behaviour yet)...

diff --git a/src/js/views/panel_view.js b/src/js/views/panel_view.js
index ef692336..8bbeac89 100644
--- a/src/js/views/panel_view.js
+++ b/src/js/views/panel_view.js
@@ -40,7 +40,7 @@
                 'change:channels change:zoom change:dx change:dy change:width change:height change:rotation change:labels change:theT change:deltaT change:theZ change:deltaZ change:z_projection change:z_start change:z_end',
                 this.render_labels);
             this.listenTo(this.model, 'change:shapes', this.render_shapes);
-            this.listenTo(this.model, 'change:outline', this.show_outline);
+            this.listenTo(this.model, 'change:outline', this.render_layout);
             // During drag or resize, model isn't updated, but we trigger 'drag'
             this.model.on('drag_resize', this.drag_resize, this);
 
@@ -63,6 +63,12 @@
                 h = xywh[3];
             if (w == this.model.get('width') && h == this.model.get('height')) {
                 // If we're only dragging - simply update position
+                var outline = this.model.get('outline');
+                if (outline != undefined) {
+                    let sw = outline.strokewidth;
+                    x = x - sw;
+                    y = y - sw;
+                }
                 this.$el.css({'top': y +'px', 'left': x +'px'});
             } else {
                 this.update_resize(x, y, w, h);
@@ -71,15 +77,6 @@
             this.$el.addClass('dragging');
         },
 
-        show_outline: function(){
-            var outline = this.model.get('outline')
-            if(outline != undefined){
-                this.$el.css({'border': 'solid ' +outline.strokewidth+'px '+outline.color})
-            }else{
-                this.$el.css({'border': '', 'outline-offset':''})
-            }
-        },
-
         render_layout: function() {
             var x = this.model.get('x'),
                 y = this.model.get('y'),
@@ -92,15 +89,31 @@
 
         update_resize: function(x, y, w, h) {
 
+            // If we have a panel border, need to adjust x,y,w,h on the page
+            // but NOT the w & h we use for img_css below.
+            var outline = this.model.get('outline');
+            var page_w = w;
+            var page_h = h;
+            if (outline != undefined) {
+                let sw = outline.strokewidth;
+                this.$el.css({'border': `solid ${sw}px ${outline.color}`})
+                x = x - sw;
+                y = y - sw;
+                page_w = w + (sw * 2);
+                page_h = h + (sw * 2);
+            } else {
+                this.$el.css({'border': '', 'outline-offset':''})
+            }
+
             // update layout of panel on the canvas
             this.$el.css({'top': y +'px',
                         'left': x +'px',
-                        'width': w +'px',
-                        'height': h +'px'});
+                        'width': page_w +'px',
+                        'height': page_h +'px'});
 
             // container needs to be square for rotation to vertical
-            $('.left_vlabels', this.$el).css('width', 3 * h + 'px');
-            $('.right_vlabels', this.$el).css('width', 3 * h + 'px');
+            $('.left_vlabels', this.$el).css('width', 3 * page_h + 'px');
+            $('.right_vlabels', this.$el).css('width', 3 * page_h + 'px');
 
             // update the img within the panel

@snoopycrimecop
Copy link
Member

Conflicting PR. Removed from build OMERO-plugins-push#167. See the console output for more details.
Possible conflicts:

--conflicts

@will-moore
Copy link
Member

Ahh - OK, after looking at the PDF export a bit closer, I realise the need for def draw_outline() - forgot that those draw methods are looked-up on the fly!

Maybe rename that and the shape type to "border" and add a comment to that method?

@@ -520,6 +523,24 @@ def __init__(self, pil_img, panel, crop):
self.scale = pil_img.size[0] / crop['width']
self.draw = ImageDraw.Draw(pil_img)

if 'border' in panel and panel['border'].get('showBorder'):
sw = panel['border'].get('strokeWidth')
shift_pos = sw / (float(panel['zoom'])/100)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the need for diving by the zoom here?
When I was testing on a big image, the zoom was very large, so the shift_pos was effectively zero and the border wasn't getting drawn around the outside of the panel. When I simply tried shift_pos = sw it seemed to work OK?

@will-moore
Copy link
Member

With #549 merged, you should be able to merge in master branch now to resolve those conflicts.

@snoopycrimecop
Copy link
Member

Conflicting PR. Removed from build OMERO-plugins-push#177. See the console output for more details.
Possible conflicts:

  • Upstream changes
    • src/templates/rois_form.template.html

--conflicts

@snoopycrimecop
Copy link
Member

Conflicting PR. Removed from build OMERO-plugins-push#178. See the console output for more details.
Possible conflicts:

  • Upstream changes
    • src/templates/rois_form.template.html

--conflicts

@snoopycrimecop
Copy link
Member

snoopycrimecop commented Sep 9, 2024

Conflicting PR. Removed from build OMERO-plugins-push#179. See the console output for more details.
Possible conflicts:

  • Upstream changes
    • src/templates/rois_form.template.html

--conflicts Conflict resolved in build OMERO-plugins-push#180. See the console output for more details.

@Rdornier
Copy link
Contributor Author

Rdornier commented Sep 9, 2024

Hi @will-moore

Thanks for the review

Maybe rename that and the shape type to "border" and add a comment to that method?

Done

I'm not sure I understand the need for diving by the zoom here?

This part has been removed to implement the nicer way you proposed for the Tiff export.
The fact I was dividing by the zoom was necessary for the PDF export to get the border at the right scale.
If I didn't divide by the zoom, it looks like this

image

@will-moore
Copy link
Member

@Rdornier I tried the PDF export and found that it's not handling image rotation very well...
Here's the PDF (left) alongside the figure. All the images have some rotation, except the black-bordered Inset:

Screenshot 2024-09-09 at 13 50 57

If you want to try and reproduce, here's the JSON for that figure, using your image from the Inset PR. If you replace the imageId values in the JSON for the ID of that image in your server, you should be able to "File > Import from JSON"

figure.json
{"version":8,"panels":[{"x":78.69643584054376,"y":388.80805822339926,"width":194.42739798392267,"height":259.3436530286886,"zoom":2254,"dx":12525.167348651237,"dy":-19057.359365077347,"labels":[],"deltaT":[],"rotation":61,"selected":false,"pixel_size_x_symbol":"µm","pixel_size_x_unit":"MICROMETER","rotation_symbol":"°","max_export_dpi":1000,"channels":[{"active":true,"coefficient":1,"color":"FF0000","emissionWave":null,"family":"linear","inverted":false,"label":"0","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"00FF00","emissionWave":null,"family":"linear","inverted":false,"label":"1","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"0000FF","emissionWave":null,"family":"linear","inverted":false,"label":"2","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}}],"datasetId":null,"datasetName":"Multiple","imageId":3502,"name":"Brightfield H-DAB Dataset.vsi [20x]","orig_height":63895,"orig_width":46622,"pixel_range":[0,255],"pixel_size_x":0.34612796434088866,"pixel_size_y":0.3461277765902012,"pixelsType":"uint8","shapes":[{"area":318197.00257172337,"height":773.0792498487598,"id":9162241,"strokeColor":"#FF0000","strokeWidth":2,"type":"Rectangle","width":546.5293299740173,"x":10549.73533501299,"y":50404.46037507562,"rotation":299},{"area":276128.0211507149,"height":525.0854815946947,"id":67428258,"strokeColor":"#000000","strokeWidth":2,"type":"Rectangle","width":525.4788493847443,"x":10523.260575307628,"y":50742.45725920265,"rotation":0}],"sizeT":1,"sizeZ":1,"theT":0,"theZ":0},{"x":302.5820580816301,"y":389.5590593706576,"width":137.34779490607372,"height":194.28185173413613,"zoom":8265,"dx":12488,"dy":-18843.5,"labels":[],"deltaT":[],"rotation":61,"selected":false,"pixel_size_x_symbol":"µm","pixel_size_x_unit":"MICROMETER","rotation_symbol":"°","max_export_dpi":1000,"border":{"color":"#FF0000","strokeWidth":10,"showBorder":true},"channels":[{"active":true,"coefficient":1,"color":"FF0000","emissionWave":null,"family":"linear","inverted":false,"label":"0","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"00FF00","emissionWave":null,"family":"linear","inverted":false,"label":"1","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"0000FF","emissionWave":null,"family":"linear","inverted":false,"label":"2","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}}],"datasetId":null,"datasetName":"Multiple","imageId":3502,"insetRoiId":9162241,"name":"Brightfield H-DAB Dataset.vsi [20x]","orig_height":63895,"orig_width":46622,"pixel_range":[0,255],"pixel_size_x":0.34612796434088866,"pixel_size_y":0.3461277765902012,"pixelsType":"uint8","shapes":[],"sizeT":1,"sizeZ":1,"theT":0,"theZ":0},{"x":302.6326743072962,"y":602.3523555986417,"width":194.42739798392267,"height":194.28185173413624,"zoom":8872.288590604028,"dx":12525,"dy":-19057.5,"labels":[],"deltaT":[],"rotation":360,"selected":false,"pixel_size_x_symbol":"µm","pixel_size_x_unit":"MICROMETER","rotation_symbol":"°","max_export_dpi":1000,"border":{"color":"#000000","strokeWidth":2,"showBorder":true},"channels":[{"active":true,"coefficient":1,"color":"FF0000","emissionWave":null,"family":"linear","inverted":false,"label":"0","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"00FF00","emissionWave":null,"family":"linear","inverted":false,"label":"1","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"0000FF","emissionWave":null,"family":"linear","inverted":false,"label":"2","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}}],"datasetId":null,"datasetName":"Multiple","imageId":3502,"insetRoiId":67428258,"name":"Brightfield H-DAB Dataset.vsi [20x]","orig_height":63895,"orig_width":46622,"pixel_range":[0,255],"pixel_size_x":0.34612796434088866,"pixel_size_y":0.3461277765902012,"pixelsType":"uint8","shapes":[],"sizeT":1,"sizeZ":1,"theT":0,"theZ":0},{"x":38.525495669603586,"y":64.53769732232564,"width":194.42739798392267,"height":259.3436530286886,"zoom":2254,"dx":12525.167348651237,"dy":-19057.359365077347,"labels":[],"deltaT":[],"rotation":61,"selected":false,"pixel_size_x_symbol":"µm","pixel_size_x_unit":"MICROMETER","rotation_symbol":"°","max_export_dpi":1000,"channels":[{"active":true,"coefficient":1,"color":"FF0000","emissionWave":null,"family":"linear","inverted":false,"label":"0","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"00FF00","emissionWave":null,"family":"linear","inverted":false,"label":"1","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"0000FF","emissionWave":null,"family":"linear","inverted":false,"label":"2","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}}],"datasetId":null,"datasetName":"Multiple","imageId":3502,"name":"Brightfield H-DAB Dataset.vsi [20x]","orig_height":63895,"orig_width":46622,"pixel_range":[0,255],"pixel_size_x":0.34612796434088866,"pixel_size_y":0.3461277765902012,"pixelsType":"uint8","shapes":[{"type":"Rectangle","x":10392.845586151354,"y":50749.41359476116,"width":548.5997081065993,"height":773.0792498487598,"area":422510.4844366586,"strokeWidth":3,"strokeColor":"#00FF00","rotation":299,"id":28784029}],"sizeT":1,"sizeZ":1,"theT":0,"theZ":0},{"x":262.504144591211,"y":65.35268833655357,"width":168.42649302350645,"height":237.34432402572293,"zoom":8265,"dx":12643.854559795347,"dy":-19188.453219685536,"labels":[],"deltaT":[],"rotation":61,"selected":false,"pixel_size_x_symbol":"µm","pixel_size_x_unit":"MICROMETER","rotation_symbol":"°","max_export_dpi":1000,"border":{"color":"#00FF00","strokeWidth":10,"showBorder":true},"channels":[{"active":true,"coefficient":1,"color":"FF0000","emissionWave":null,"family":"linear","inverted":false,"label":"0","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"00FF00","emissionWave":null,"family":"linear","inverted":false,"label":"1","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"0000FF","emissionWave":null,"family":"linear","inverted":false,"label":"2","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}}],"datasetId":null,"datasetName":"Multiple","imageId":3502,"insetRoiId":28784029,"name":"Brightfield H-DAB Dataset.vsi [20x]","orig_height":63895,"orig_width":46622,"pixel_range":[0,255],"pixel_size_x":0.34612796434088866,"pixel_size_y":0.3461277765902012,"pixelsType":"uint8","shapes":[{"type":"Rectangle","strokeWidth":2,"strokeColor":"#FF00FF","x":10454.322035915362,"y":51050.831393755645,"width":182.17644332467248,"height":182.17644332467248,"id":2439834,"rotation":299},{"type":"Rectangle","strokeWidth":2,"strokeColor":"#FFFFFF","x":10575.911778337664,"y":51044.911778337664,"width":182.17644332467248,"height":182.17644332467225,"id":56477384,"rotation":299}],"sizeT":1,"sizeZ":1,"theT":0,"theZ":0},{"x":430.93063761471745,"y":65.35268833655357,"width":137.34779490607372,"height":137.34779490607372,"zoom":25591.673187356544,"dx":12765.589742422302,"dy":-19194.41961541798,"labels":[],"deltaT":[],"rotation":61,"selected":false,"pixel_size_x_symbol":"µm","pixel_size_x_unit":"MICROMETER","rotation_symbol":"°","max_export_dpi":1000,"border":{"color":"#FF00FF","strokeWidth":0.25,"showBorder":true},"channels":[{"active":true,"coefficient":1,"color":"FF0000","emissionWave":null,"family":"linear","inverted":false,"label":"0","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"00FF00","emissionWave":null,"family":"linear","inverted":false,"label":"1","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"0000FF","emissionWave":null,"family":"linear","inverted":false,"label":"2","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}}],"datasetId":null,"datasetName":"Multiple","imageId":3502,"insetRoiId":2439834,"name":"Brightfield H-DAB Dataset.vsi [20x]","orig_height":63895,"orig_width":46622,"pixel_range":[0,255],"pixel_size_x":0.34612796434088866,"pixel_size_y":0.3461277765902012,"pixelsType":"uint8","shapes":[],"sizeT":1,"sizeZ":1,"theT":0,"theZ":0},{"x":361.75547083720585,"y":233.52184558476498,"width":69.1751667775116,"height":69.17516677751152,"zoom":25591.673187356544,"dx":12644,"dy":-19188.5,"labels":[],"deltaT":[],"rotation":61,"selected":false,"pixel_size_x_symbol":"µm","pixel_size_x_unit":"MICROMETER","rotation_symbol":"°","max_export_dpi":1000,"border":{"color":"#FFFFFF","strokeWidth":5,"showBorder":true},"channels":[{"active":true,"coefficient":1,"color":"FF0000","emissionWave":null,"family":"linear","inverted":false,"label":"0","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"00FF00","emissionWave":null,"family":"linear","inverted":false,"label":"1","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}},{"active":true,"coefficient":1,"color":"0000FF","emissionWave":null,"family":"linear","inverted":false,"label":"2","reverseIntensity":false,"window":{"end":255,"max":255,"min":0,"start":0}}],"datasetId":null,"datasetName":"Multiple","imageId":3502,"insetRoiId":56477384,"name":"Brightfield H-DAB Dataset.vsi [20x]","orig_height":63895,"orig_width":46622,"pixel_range":[0,255],"pixel_size_x":0.34612796434088866,"pixel_size_y":0.3461277765902012,"pixelsType":"uint8","shapes":[],"sizeT":1,"sizeZ":1,"theT":0,"theZ":0}],"paper_width":595,"paper_height":842,"page_size":"A4","page_count":"2","paper_spacing":50,"page_col_count":"2","height_mm":297,"width_mm":210,"orientation":"vertical","legend":"","legend_collapsed":true,"page_color":"ffffff","figureName":"Big Image with Insets","fileId":20808}

The TIFF export worked better, but still the issue with rotated ROIs. This is possibly a bug from the Inset PR - I'll look into it...

One other fix is needed, when the border is a fraction of a pt (e.g. 0.25 pt in that case) we need to round to int for TIFF export:

+++ b/omero_figure/scripts/omero/figure_scripts/Figure_To_Pdf.py
@@ -2278,7 +2278,7 @@ class TiffExport(FigureExport):
         # Add border if needed - Rectangle around the whole panel
         if 'border' in panel and panel['border'].get('showBorder'):
             sw = panel['border'].get('strokeWidth')
-            border_width = scale_to_export_dpi(sw)
+            border_width = int(round(scale_to_export_dpi(sw)))

@will-moore
Copy link
Member

Here is the 'rotation' fix which addresses the rotation of 'rectangle' Shapes within panels (not due to this PR). Line numbers apply to main branch rather than this PR, but hopefully not to hard to find:

+++ b/omero_figure/scripts/omero/figure_scripts/Figure_To_Pdf.py
@@ -216,6 +216,30 @@ class ShapeExport(object):
             point[0] * tf['A10'] + point[1] * tf['A11'] + tf['A12'],
         ] if tf else point
 
+    @staticmethod
+    def apply_rotation(point, centre, rotation):
+        cx = centre[0]
+        cy = centre[1]
+        x = point[0]
+        y = point[1]
+
+        dx = cx - x
+        dy = cy - y
+        # distance of point from centre of rotation
+        h = sqrt(dx * dx + dy * dy)
+        # and the angle
+        angle1 = atan2(dx, dy)
+
+        # Add the rotation to the angle and calculate new
+        # opposite and adjacent lengths from centre of rotation
+        angle2 = angle1 - radians(rotation)
+        newo = sin(angle2) * h
+        newa = cos(angle2) * h
+        # to give correct x and y within cropped panel
+        x = cx - newo
+        y = cy - newa
+        return x, y
+
     def draw_rectangle(self, shape):
         # to support rotation/transforms, convert rectangle to a simple
         # four point polygon and draw that instead
@@ -227,6 +251,17 @@ class ShapeExport(object):
             (shape['x'] + shape['width'], shape['y'] + shape['height']),
             (shape['x'], shape['y'] + shape['height']),
         ]
+
+        if shape.get('rotation' or 0) != 0:
+            rotation = shape.get('rotation')
+            # rotate around centre of rectangle
+            cx = shape['x'] + shape['width'] / 2
+            cy = shape['y'] + shape['height'] / 2
+            points = [
+                self.apply_rotation(point, [cx, cy], rotation)
+                for point in points
+            ]
+
         s['points'] = ' '.join(','.join(
             map(str, self.apply_transform(t, point))) for point in points)
         self.draw_polygon(s)
@@ -633,6 +668,15 @@ class ShapeToPilExport(ShapeExport):
             (shape['x'], shape['y'] + shape['height']),
         ]
         p = []
+        if shape.get('rotation' or 0) != 0:
+            rotation = shape.get('rotation')
+            # rotate around centre of rectangle
+            cx = shape['x'] + shape['width'] / 2
+            cy = shape['y'] + shape['height'] / 2
+            points = [
+                self.apply_rotation(point, [cx, cy], rotation)
+                for point in points
+            ]
         t = shape.get('transform')
         for point in points:
             transformed = self.apply_transform(t, point)

@will-moore
Copy link
Member

Hi @Rdornier - that's nearly there, but the borders just need expanding by 1/2 their thickness so that they are completely outside the edges of the panel. Compare the figure and pdf:

Screenshot 2024-09-12 at 09 54 13

@Rdornier
Copy link
Contributor Author

Hi @will-moore
I've tried to fix it by removing the division by the zoom factor and mutiplying the strokewidth by 1.5.
It works quite good for me with big images. However, for small images, I still have the bug described in #578 (comment).

I'm wondering if it is again my dev env which just palying tricks with me.
Can you check using one of the image from this zenodo if you also experience the same bug ?

@will-moore will-moore added this to the 7.2.0 milestone Oct 3, 2024
@snoopycrimecop
Copy link
Member

snoopycrimecop commented Oct 11, 2024

Conflicting PR. Removed from build OMERO-plugins-push#208. See the console output for more details.
Possible conflicts:

--conflicts Conflict resolved in build OMERO-plugins-push#209. See the console output for more details.

@will-moore
Copy link
Member

Tested export again. TIFF is fine but PDF the borders are not quite right. I tried tweaking the stroke-width etc but it's getting distorted by the crop and draw_rectangle logic.
Instead I reverted to drawing the border directly onto the page and this is looking good. See what you think:

+++ b/omero_figure/scripts/omero/figure_scripts/Figure_To_Pdf.py
@@ -1177,26 +1177,40 @@ class FigureExport(object):
         Add any Shapes
         """
         if 'border' in panel and panel['border'].get('showBorder'):
-            crop = self.get_crop_region(panel)
-            sw = panel['border'].get('strokeWidth')
-            shift_pos = 1.5*sw
-
-            shape = {}
-            shape['strokeColor'] = panel['border'].get('color')
-            shape['strokeWidth'] = sw
-            shape['x'] = crop['x'] - shift_pos
-            shape['y'] = crop['y'] - shift_pos
-            shape['width'] = crop['width'] + 2 * shift_pos
-            shape['height'] = crop['height'] + 2 * shift_pos
-            shape['type'] = "border"
-            rotation = panel['rotation']
-            if rotation != 0:
-                shape['rotation'] = 360 - rotation
-
-            if "shapes" not in panel:
-                panel['shapes'] = [shape]
-            else:
-                panel['shapes'].append(shape)
+            stroke_width = panel['border'].get('strokeWidth')
+            r, g, b, a = ShapeExport.get_rgba(panel['border'].get('color'))
+            canvas = self.figure_canvas
+            canvas.setStrokeColorRGB(r, g, b, alpha=a)
+            canvas.setLineWidth(stroke_width)
+
+            # by default, line is drawn in the middle of the path
+            # we want it to be on the outside of the xywh coords
+            shift_pos = stroke_width / 2
+
+            p = canvas.beginPath()
+            x = panel['x'] - shift_pos
+            y = panel['y'] - shift_pos
+            width = panel['width'] + (shift_pos * 2)
+            height = panel['height'] + (shift_pos * 2)
+
+            # Handle page offsets
+            x = x - page['x']
+            y = y - page['y']
+
+            # rectangle around the panel
+            points = [[x, y], [x + width, y], [x + width, y + height], [x, y + height]]
+
+            # flip the y coordinate
+            for point in points:
+                point[1] = self.page_height - point[1]
+            
+            # same logic as draw_polygon()
+            p.moveTo(points[0][0], points[0][1])
+            for point in points[1:]:
+                p.lineTo(point[0], point[1])
+            for point in points[0:2]:
+                p.lineTo(point[0], point[1])
+            canvas.drawPath(p, fill=0, stroke=1)
 
         if "shapes" not in panel:

This means you could revert other changes such as in_panel_check and:

    def draw_border(self, shape):
        self.draw_rectangle(shape, False)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants