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

Colorbar feature #605

Open
wants to merge 42 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f646b8a
calibration bar proof of concept
MinaEnayat Nov 18, 2024
9fed89d
Calib bar code cleanup
MinaEnayat Nov 18, 2024
f87bf2c
Added dynamic position and adjustable thickness of the bar
MinaEnayat Nov 22, 2024
9164684
colobar implementation start in python export script
Tom-TBT Nov 23, 2024
51318ba
colorbar ticks and labels
Tom-TBT Nov 23, 2024
4d6baa9
cosmetic adjustments, changed contour to single tick line
Tom-TBT Nov 26, 2024
d73d392
Code clean up
MinaEnayat Nov 29, 2024
361f5c6
Adding ticks to the calibration bar
MinaEnayat Nov 29, 2024
9c79d2c
Code clean up
MinaEnayat Nov 29, 2024
7fcc97d
Update tick color and adjust tick label font size
MinaEnayat Nov 29, 2024
ce2575e
Dynamically changing no. of ticks
MinaEnayat Nov 29, 2024
4c1401f
Add dynamic logic for calibration bar: lookup or simple color
MinaEnayat Nov 29, 2024
7343f92
The styling of positions excep top
MinaEnayat Nov 29, 2024
6e8573d
Adjust CSS for top position and add margin/spacing between image and …
MinaEnayat Dec 1, 2024
9ec814c
Merge branch 'calib_bar' of github.com:MinaEnayat/omero-figure into c…
MinaEnayat Dec 1, 2024
b822d87
Merge remote-tracking branch 'ome/master' into calib_bar
Tom-TBT Dec 1, 2024
7acbcde
adjust to use dynamic lut after merge
Tom-TBT Dec 1, 2024
51b1b7a
loading of calib_bar if lutpicker not opened yet, eg saved figure
Tom-TBT Dec 2, 2024
aa28434
Adding defaults value, renaming, adding space between tick and label,…
MinaEnayat Dec 3, 2024
d6131a9
Adding defaults value, renaming, adding space between tick and label,…
MinaEnayat Dec 3, 2024
52b2952
Adding Inverse colorbar
MinaEnayat Dec 3, 2024
37ee30e
Adding Inverse colorbar - for Lut
MinaEnayat Dec 3, 2024
bf550d2
set lut_png url to handle static and dynamic
Tom-TBT Dec 5, 2024
19b8dd9
update export script with new param
Tom-TBT Dec 5, 2024
39f72f2
rename parameters
Tom-TBT Dec 5, 2024
e9992dc
minor fixes
Tom-TBT Dec 5, 2024
aeddbf9
simplifed template, CSS and handle resize
Tom-TBT Dec 5, 2024
8925e53
resize and pdf bug fix
Tom-TBT Dec 5, 2024
c9e82aa
fixed color gradient
Tom-TBT Dec 5, 2024
f04ae8d
changed isLUT test
Tom-TBT Dec 5, 2024
ced78e2
crop page around panel with colorbar
Tom-TBT Dec 6, 2024
906adb6
vertical label layout
Tom-TBT Dec 7, 2024
4ec7fcd
colorbar horizontal labels
Tom-TBT Dec 8, 2024
33c4dd0
inverted label fix
Tom-TBT Dec 9, 2024
ab42e78
fix colorbar label when start color != 0
Tom-TBT Dec 9, 2024
c50959d
lowercase script_service var name
Tom-TBT Dec 9, 2024
04f9f0b
refactored colorbar template and css
Tom-TBT Dec 11, 2024
a67be63
added overlap between elements
Tom-TBT Dec 11, 2024
bafb394
removed default param to loadLuts()
Tom-TBT Dec 11, 2024
ed6d64d
crop page to suit all label orientation and colorbar labels
Tom-TBT Dec 13, 2024
68aa191
colorbar label dynamic decimal rounding
Tom-TBT Dec 14, 2024
bef7126
parametrized output in python
Tom-TBT Dec 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 234 additions & 13 deletions omero_figure/scripts/omero/figure_scripts/Figure_To_Pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
from reportlab.lib.colors import Color
from reportlab.platypus import Paragraph
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT

from reportlab.lib.utils import ImageReader
reportlab_installed = True
except ImportError:
reportlab_installed = False
Expand Down Expand Up @@ -1677,6 +1677,222 @@ def draw_scalebar(self, panel, region_width, page):
font_size, (red, green, blue),
align="center")

def get_color_ramp(self, channel):
"""
Return the 256 1D array of the LUT from the server or
the color gradient.

LUT files on the server are read with the script service, and
file content is parsed with a custom implementation.
"""
color = channel["color"]

# Convert the hexadecimal string to RGB
color_ramp = None
if len(color) == 6:
try:
r = int(color[0:2], 16)
g = int(color[2:4], 16)
b = int(color[4:6], 16)
color_ramp = (numpy.linspace(0, 1, 256).reshape(-1, 1)
* numpy.array([r, g, b], dtype=numpy.uint8).T)
color_ramp = color_ramp.astype(numpy.uint8)
except ValueError:
pass

else:
script_service = self.conn.getScriptService()
luts = script_service.getScriptsByMimetype("text/x-lut")
for lut in luts:
if lut.name.val != color:
continue

orig_file = self.conn.getObject(
"OriginalFile", lut.getId()._val)
lut_data = bytearray()
# Collect the LUT data in byte form
for chunk in orig_file.getFileInChunks():
lut_data.extend(chunk)

if len(lut_data) in [768, 800]:
lut_arr = numpy.array(lut_data, dtype="uint8")[-768:]
color_ramp = lut_arr.reshape(3, 256).T
else:
lut_data = lut_data.decode()
r, g, b = [], [], []

lines = lut_data.split("\n")
sep = None
if "\t" in lines[0]:
sep = "\t"
for line in lines:
val = line.split(sep)
if len(val) < 3 or not val[-1].isnumeric():
continue
r.append(int(val[-3]))
g.append(int(val[-2]))
b.append(int(val[-1]))
color_ramp = numpy.array([r, g, b], dtype=numpy.uint8).T
break

if channel.get("reverseIntensity", False):
color_ramp = color_ramp[::-1]

if color_ramp is None:
return numpy.zeros((1, 256, 3), dtype=numpy.uint8)
else:
return color_ramp[numpy.newaxis]

def draw_colorbar(self, panel, page):
"""
Add the colorbar to the page.
Here we calculate the position of colorbar but delegate
to self.draw_scalebar_line() and self.draw_text() to actually place
the colorbar, ticks and labels on PDF/TIFF
"""

colorbar = panel.get("colorbar", {})
if not colorbar.get("show", False):
return

channel = None
for c in panel["channels"]:
if c["active"]:
channel = c
break
if not channel:
return

color_ramp = self.get_color_ramp(channel)

spacing = colorbar["spacing"]
thickness = colorbar["thickness"]
cbar = { # Dict of colorbar properties to pass to paste_image
'zoom': '100',
'dx': 0,
'dy': 0,
'orig_height': panel['orig_height'],
'orig_width': panel['orig_width'],
}
start, end = channel["window"]["start"], channel["window"]["end"]

decimals = max(0, int(numpy.ceil(
-numpy.log10((end - start) / colorbar["num_ticks"]))))
labels = numpy.linspace(start, end, num=colorbar["num_ticks"])
pos_ratio = numpy.linspace(0, 1, colorbar["num_ticks"])
if colorbar["position"] in ["left", "right"]:
color_ramp = color_ramp.transpose((1, 0, 2))[::-1]
cbar['width'] = thickness
cbar['height'] = panel['height']
cbar['y'] = panel['y']
cbar['x'] = panel['x'] - (spacing + thickness)
labels_x = [cbar['x']]
labels_y = cbar['y'] + panel['height'] * pos_ratio[::-1]
align = "right"
if colorbar["position"] == "right":
cbar['x'] = panel['x'] + panel['width'] + spacing
labels_x = [cbar['x'] + cbar['width']]
align = "left"
labels_x *= labels.size # Duplicate x postions
elif colorbar["position"] in ["top", "bottom"]:
cbar['width'] = panel['width']
cbar['height'] = thickness
cbar['x'] = panel['x']
cbar['y'] = panel['y'] - (spacing + thickness)
labels_x = cbar['x'] + panel['width'] * pos_ratio
labels_y = [cbar['y']]
align = "center"
if colorbar["position"] == "bottom":
cbar['y'] = panel['y'] + panel['height'] + spacing
labels_y = [cbar['y'] + cbar['height']]
labels_y *= labels.size # Duplicate y postions

pil_img = Image.fromarray(color_ramp)
img_name = channel["color"] + ".png"

# for PDF export, we might have a target dpi
dpi = panel.get('min_export_dpi', None)

# Paste the panel to PDF or TIFF image
self.paste_image(pil_img, img_name, cbar, page, dpi,
is_colorbar=True)

rgb = tuple(int(colorbar["axis_color"][i:i+2], 16) for i in (0, 2, 4))
fontsize = int(colorbar["font_size"])
tick_width = 1
tick_len = colorbar["tick_len"]
label_margin = colorbar["label_margin"]
contour_width = tick_width
for label, pos_x, pos_y in zip(labels, labels_x, labels_y):

# Cosmetic correction, for first and last ticks to be
# aligned with the image
shift = 0
if label == labels[0]:
shift = -tick_width / 2
elif label == labels[-1]:
shift = tick_width / 2

# Round the label str to the appropriate decimal
label = f"{label:.{decimals}f}"

if colorbar["position"] == "left":
x2 = pos_x - tick_len
pos_y += shift
self.draw_scalebar_line(pos_x, pos_y, x2, pos_y,
tick_width, rgb)
self.draw_text(label, pos_x - 4 - label_margin,
pos_y - fontsize / 2 + 1,
fontsize, rgb, align=align)
elif colorbar["position"] == "right":
x2 = pos_x + tick_len
pos_y += shift
self.draw_scalebar_line(pos_x, pos_y, x2, pos_y,
tick_width, rgb)
self.draw_text(label, pos_x + 4 + label_margin,
pos_y - fontsize / 2 + 1,
fontsize, rgb, align=align)
elif colorbar["position"] == "top":
y2 = pos_y - tick_len
pos_x -= shift # Order of the label is reversed
self.draw_scalebar_line(pos_x, pos_y, pos_x, y2,
tick_width, rgb)
self.draw_text(label, pos_x,
pos_y - fontsize - 2 - label_margin,
fontsize, rgb, align=align)
elif colorbar["position"] == "bottom":
y2 = pos_y + tick_len
pos_x -= shift # Order of the label is reversed
self.draw_scalebar_line(pos_x, pos_y, pos_x, y2,
tick_width, rgb)
self.draw_text(label, pos_x, pos_y + 4 + label_margin,
fontsize, rgb, align=align)

if colorbar["position"] == "top":
self.draw_scalebar_line(cbar['x'],
cbar['y'],
cbar['x'] + cbar['width'],
cbar['y'],
contour_width, rgb)
elif colorbar["position"] == "bottom":
self.draw_scalebar_line(cbar['x'],
cbar['y'] + cbar['height'],
cbar['x'] + cbar['width'],
cbar['y'] + cbar['height'],
contour_width, rgb)
elif colorbar["position"] == "left":
self.draw_scalebar_line(cbar['x'],
cbar['y'],
cbar['x'],
cbar['y'] + cbar['height'],
contour_width, rgb)
elif colorbar["position"] == "right":
self.draw_scalebar_line(cbar['x'] + cbar['width'],
cbar['y'],
cbar['x'] + cbar['width'],
cbar['y'] + cbar['height'],
contour_width, rgb)

def is_big_image(self, image):
"""Return True if this is a 'big' tiled image."""
max_w, max_h = self.conn.getMaxPlaneSize()
Expand Down Expand Up @@ -1936,12 +2152,6 @@ def draw_panel(self, panel, page, idx):
"""
image_id = panel['imageId']
channels = panel['channels']
x = panel['x']
y = panel['y']

# Handle page offsets
x = x - page['x']
y = y - page['y']

image = self.conn.getObject("Image", image_id)
if image is None:
Expand Down Expand Up @@ -2150,6 +2360,7 @@ def add_panels_to_page(self, panels_json, image_ids, page):
# Finally, add scale bar and labels to the page
self.draw_scalebar(panel, pil_img.size[0], page)
self.draw_labels(panel, page)
self.draw_colorbar(panel, page)

def get_figure_file_ext(self):
return "pdf"
Expand Down Expand Up @@ -2266,7 +2477,8 @@ def draw_scalebar_line(self, x, y, x2, y2, width, rgb):
c.setStrokeColorRGB(red, green, blue, 1)
c.line(x, y, x2, y2)

def paste_image(self, pil_img, img_name, panel, page, dpi):
def paste_image(self, pil_img, img_name, panel, page, dpi,
is_colorbar=False):
""" Adds the PIL image to the PDF figure. Overwritten for TIFFs """

# Apply flip transformations before drawing the image
Expand Down Expand Up @@ -2307,8 +2519,15 @@ def paste_image(self, pil_img, img_name, panel, page, dpi):
if self.zip_folder_name is not None:
img_name = os.path.join(self.zip_folder_name, FINAL_DIR, img_name)

# Save Image to file, then bring into PDF
pil_img.save(img_name)
if is_colorbar:
# Save the image to a BytesIO stream
buffer = BytesIO()
pil_img.save(buffer, format="PNG")
buffer.seek(0)
img_name = ImageReader(buffer) # drawImage accepts ImageReader
else:
# Save Image to file, then bring into PDF
pil_img.save(img_name)
# Since coordinate system is 'bottom-up', convert from 'top-down'
y = self.page_height - height - y
# set fill color alpha to fully opaque, since this impacts drawImage
Expand Down Expand Up @@ -2374,7 +2593,8 @@ def add_page_color(self):
""" Don't need to do anything for TIFF. Image is already colored."""
pass

def paste_image(self, pil_img, img_name, panel, page, dpi=None):
def paste_image(self, pil_img, img_name, panel, page,
dpi=None, is_colorbar=False):
""" Add the PIL image to the current figure page """

# Apply flip transformations before drawing the image
Expand Down Expand Up @@ -2405,16 +2625,17 @@ def paste_image(self, pil_img, img_name, panel, page, dpi=None):
width = int(round(width))
height = int(round(height))

export_img = self.export_images and not is_colorbar
# Save image BEFORE resampling
if self.export_images:
if export_img:
rs_name = os.path.join(self.zip_folder_name, RESAMPLED_DIR,
img_name)
pil_img.save(rs_name)

# Resize to our target size to match DPI of figure
pil_img = pil_img.resize((width, height), Image.BICUBIC)

if self.export_images:
if export_img:
img_name = os.path.join(self.zip_folder_name, FINAL_DIR, img_name)
pil_img.save(img_name)

Expand Down
Loading