fix(LamNI): use the real setup logo in the PDF report, fix column gaps

Replace the ASCII-art header with the actual LamNI.png logo, embedded
via PDFWriter's underlying fpdf object (bec_lib's PDFWriter has no
public image API). Also fix the label/value formatting: values were
right-justified in a wide fixed field, leaving a big ragged gap after
short labels -- left-justify both instead for a clean, tight
"label: value" layout.

Also fixes a real bug found along the way: the scilog attachment
referenced "LamNI_logo.png", a file that has never existed (the actual
file is LamNI.png) -- the resulting FileNotFoundError was swallowed by
write_pdf_report()'s generic try/except, so the scilog message has
been silently failing to send every time. Now uses the one correct,
shared logo_path for both the PDF and the scilog attachment.
This commit is contained in:
x01dc
2026-07-27 17:13:49 +02:00
parent eb61df1c93
commit f8ced692d3
@@ -2145,17 +2145,15 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools
def write_pdf_report(self):
"""Create and write the PDF report with current LamNI settings."""
dev = builtins.__dict__.get("dev")
header = (
" \n" * 3
+ " ::: ::: ::: ::: :::: ::: ::::::::::: \n"
+ " :+: :+: :+: :+:+: :+:+: :+:+: :+: :+: \n"
+ " +:+ +:+ +:+ +:+ +:+:+ +:+ :+:+:+ +:+ +:+ \n"
+ " +#+ +#++:++#++: +#+ +:+ +#+ +#+ +:+ +#+ +#+ \n"
+ " +#+ +#+ +#+ +#+ +#+ +#+ +#+#+# +#+ \n"
+ " #+# #+# #+# #+# #+# #+# #+#+# #+# \n"
+ " ########## ### ### ### ### ### #### ########### \n"
)
padding = 20
# LamNI.png (not the previously-referenced, nonexistent
# "LamNI_logo.png" -- that typo silently broke the scilog logo
# attachment below, since the resulting FileNotFoundError was caught
# by the generic try/except and never surfaced).
logo_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "LamNI.png")
# Widest label below ("Number of individual sub-tomograms:") is 36
# chars; left-justify both label and value (no right-justify) so
# short values don't leave a big ragged gap after the label.
padding = 38
piezo_range = f"{self.lamni_piezo_range_x:.2f}/{self.lamni_piezo_range_y:.2f}"
stitching = f"{self.lamni_stitch_x:.2f}/{self.lamni_stitch_y:.2f}"
dataset_id = str(self.client.queue.next_dataset_number)
@@ -2169,32 +2167,39 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools
except Exception:
energy_str = "N/A"
content = [
f"{'Sample Name:':<{padding}}{self.sample_name:>{padding}}\n",
f"{'Measurement ID:':<{padding}}{str(self.tomo_id):>{padding}}\n",
f"{'Dataset ID:':<{padding}}{dataset_id:>{padding}}\n",
f"{'Sample Info:':<{padding}}{'Sample Info':>{padding}}\n",
f"{'e-account:':<{padding}}{str(self.client.username):>{padding}}\n",
f"{'Number of projections:':<{padding}}{report_total_projections:>{padding}}\n",
f"{'First scan number:':<{padding}}{self.client.queue.next_scan_number:>{padding}}\n",
f"{'Last scan number approx.:':<{padding}}{self.client.queue.next_scan_number + report_total_projections + 10:>{padding}}\n",
f"{'Current photon energy:':<{padding}}{energy_str:>{padding}}\n",
f"{'Exposure time:':<{padding}}{self.tomo_countingtime:>{padding}.2f}\n",
f"{'Fermat spiral step size:':<{padding}}{self.tomo_shellstep:>{padding}.2f}\n",
f"{'Piezo range (FOV sample plane):':<{padding}}{piezo_range:>{padding}}\n",
f"{'Restriction to circular FOV:':<{padding}}{self.tomo_circfov:>{padding}.2f}\n",
f"{'Stitching:':<{padding}}{stitching:>{padding}}\n",
f"{'Number of individual sub-tomograms:':<{padding}}{8:>{padding}}\n",
f"{'Angular step within sub-tomogram:':<{padding}}{self.tomo_angle_stepsize:>{padding}.2f}\n",
f"{'Tomo type:':<{padding}}{self.tomo_type:>{padding}}\n",
f"{'Sample Name:':<{padding}}{self.sample_name}\n",
f"{'Measurement ID:':<{padding}}{self.tomo_id}\n",
f"{'Dataset ID:':<{padding}}{dataset_id}\n",
f"{'Sample Info:':<{padding}}Sample Info\n",
f"{'e-account:':<{padding}}{self.client.username}\n",
f"{'Number of projections:':<{padding}}{report_total_projections}\n",
f"{'First scan number:':<{padding}}{self.client.queue.next_scan_number}\n",
f"{'Last scan number approx.:':<{padding}}"
f"{self.client.queue.next_scan_number + report_total_projections + 10}\n",
f"{'Current photon energy:':<{padding}}{energy_str}\n",
f"{'Exposure time:':<{padding}}{self.tomo_countingtime:.2f}\n",
f"{'Fermat spiral step size:':<{padding}}{self.tomo_shellstep:.2f}\n",
f"{'Piezo range (FOV sample plane):':<{padding}}{piezo_range}\n",
f"{'Restriction to circular FOV:':<{padding}}{self.tomo_circfov:.2f}\n",
f"{'Stitching:':<{padding}}{stitching}\n",
f"{'Number of individual sub-tomograms:':<{padding}}8\n",
f"{'Angular step within sub-tomogram:':<{padding}}{self.tomo_angle_stepsize:.2f}\n",
f"{'Tomo type:':<{padding}}{self.tomo_type}\n",
]
hook_description = self._describe_active_hook()
if hook_description:
content.append(f"{'At-each-angle hook:':<{padding}}{hook_description:>{padding}}\n")
content.append(f"{'At-each-angle hook:':<{padding}}{hook_description}\n")
content = "".join(content)
hook_source = self._active_hook_source()
user_target = os.path.expanduser(f"~/data/raw/documentation/tomo_scan_ID_{self.tomo_id}.pdf")
with PDFWriter(user_target) as file:
file.write(header)
# PDFWriter (bec_lib) has no public image API -- reach into its
# underlying fpdf object directly. logo_w chosen to keep the
# header modest relative to the A4 page width (210mm).
if os.path.exists(logo_path):
logo_w = 50
file._pdf.image(logo_path, x=(210 - logo_w) / 2, w=logo_w)
file._pdf.ln(5)
file.write(content)
if hook_source:
file.write(
@@ -2213,7 +2218,6 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools
f"\n\nAt-each-angle hook source ('{self.at_each_angle_hook}'):\n{hook_source}"
)
msg = bec.logbook.LogbookMessage()
logo_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "LamNI_logo.png")
msg.add_file(logo_path).add_text(scilog_text.replace("\n", "</p><p>")).add_tag(
["BEC", "tomo_parameters", f"dataset_id_{dataset_id}", "LamNI", self.sample_name]
)