Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Binary file added nanomath/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file added nanomath/__pycache__/nanomath.cpython-310.pyc
Binary file not shown.
23 changes: 13 additions & 10 deletions nanoplot/NanoPlot.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@

from os import path
import logging
import sys
import nanoplot.utils as utils
from nanoplot.version import __version__
import sys
from nanoplotter.plot import Plot


def main():
Expand All @@ -26,11 +27,13 @@ def main():
-calls plotting function
"""
settings, args = utils.get_args()
# Thread CLI --dpi into settings so static export honors it
if hasattr(args, "dpi") and args.dpi:
settings["dpi"] = int(args.dpi)
import nanoplot.report as report
from nanoget import get_input
from nanoplot.filteroptions import filter_and_transform_data
try:
import nanoplot.report as report
from nanoget import get_input
from nanoplot.filteroptions import filter_and_transform_data
from nanoplotter.plot import Plot
utils.make_output_dir(args.outdir)
import pickle
utils.init_logs(args)
Expand Down Expand Up @@ -74,7 +77,8 @@ def main():

settings["statsfile"] = [make_stats(datadf, settings, suffix="", tsv_stats=args.tsv_stats)]
datadf, settings = filter_and_transform_data(datadf, settings)
if settings["filtered"]: # Bool set when filter was applied in filter_and_transform_data()
# Bool set when filter was applied in filter_and_transform_data()
if settings["filtered"]:
settings["statsfile"].append(
make_stats(
datadf[datadf["length_filter"]],
Expand All @@ -85,8 +89,7 @@ def main():
)

if args.only_report:
Plot.only_report = True

Plot.only_report = True
if args.barcoded:
main_path = settings["path"]
for barc in list(datadf["barcode"].unique()):
Expand Down Expand Up @@ -382,7 +385,7 @@ def make_report(plots, settings):
which is parsed to a table (rather dodgy) or nicely if it's a pandas/tsv
"""
logging.info("Writing html report.")
from nanoplot import report
import nanoplot.report as report

html_content = [
'<body class="grid">',
Expand All @@ -397,4 +400,4 @@ def make_report(plots, settings):


if __name__ == "__main__":
main()
main()
21 changes: 20 additions & 1 deletion nanoplot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from nanoplot.version import __version__
from argparse import HelpFormatter, Action, ArgumentParser
import textwrap as _textwrap
import plotly.io as pio


class CustomHelpFormatter(HelpFormatter):
Expand Down Expand Up @@ -229,7 +230,8 @@ def get_args():
visual.add_argument(
"--font_scale", help="Scale the font of the plots by a factor", type=float, default=1
)
visual.add_argument("--dpi", help="Set the dpi for saving images", type=int, default=100)
# CHANGED: default dpi to 300
visual.add_argument("--dpi", help="Set the dpi for saving images", type=int, default=300)
visual.add_argument(
"--hide_stats",
help="Not adding Pearson R stats in some bivariate plots",
Expand Down Expand Up @@ -368,3 +370,20 @@ def subsample_datasets(df, minimal=10000):
subsampled_df = df.sample(minimal)

return subsampled_df


# NEW: DPI-aware Plotly static export helper
def write_static_image(fig, outpath, dpi=300, default_inches=(6.4, 4.8)):
width_px = int(default_inches[0] * dpi)
height_px = int(default_inches[1] * dpi)

pio.write_image(fig, outpath, width=width_px, height=height_px, scale=1)

lower = outpath.lower()
if lower.endswith((".png", ".jpg", ".jpeg", ".tif", ".tiff", ".webp")):
try:
from PIL import Image
im = Image.open(outpath)
im.save(outpath, dpi=(dpi, dpi))
except Exception as e:
logging.warning("Could not set DPI metadata for %s: %s", outpath, e)
119 changes: 82 additions & 37 deletions nanoplotter/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,32 @@
import sys
import logging

# Use Plotly's Chrome bootstrapper instead of kaleido
import plotly.io as pio
try:
# This should find or install a compatible Chrome in a user-writable location
pio.get_chrome()
except Exception as e:
logging.warning(
"Plotly could not fetch or find Chrome automatically. "
"Static exports may fail unless BROWSER_PATH is set. Details: %s", e
)

# DPI-aware writer
try:
from nanoplot.utils import write_static_image
except Exception as e:
logging.warning("Could not import write_static_image from nanoplot.utils: %s", e)
write_static_image = None


class Plot(object):
"""A Plot object is defined by a path to the output file and the title of the plot."""

only_report = False

def __init__(self, path, title):
self.path = path
self.title = title
self.fig = None
self.fig = None # Plotly fig for HTML/static; Matplotlib Figure in legacy mode
self.html = None

def encode(self):
Expand All @@ -40,60 +46,99 @@ def encode1(self):
return '<img src="data:image/png;base64,{0}">'.format(data_uri)

def encode2(self):
# Legacy Matplotlib path only
buf = BytesIO()
self.fig.savefig(buf, format="png", bbox_inches="tight", dpi=100)
buf.seek(0)
string = b64encode(buf.read())
return '<img src="data:image/png;base64,{0}">'.format(urlquote(string))

def save(self, settings):
if not self.only_report:
if self.html:
with open(self.path, "w") as html_out:
html_out.write(self.html)
if not settings["no_static"]:
try:
for fmt in settings["format"]:
self.save_static(fmt)
except (AttributeError, ValueError) as e:
p = os.path.splitext(self.path)[0] + ".png"
if os.path.exists(p):
os.remove(p)
logging.warning("No static plots are saved due to an export problem:")
logging.warning(e)

elif self.fig:
if isinstance(settings["format"], list):
for fmt in settings["format"]:
self.fig.savefig(
fname=self.path + "." + fmt,
format=fmt,
bbox_inches="tight",
)
else:
if self.only_report:
return

if self.html:
# Save the interactive HTML
with open(self.path, "w") as html_out:
html_out.write(self.html)

# Also save static images unless suppressed
if not settings.get("no_static", False):
try:
fmts = settings.get("format", ["png"])
for fmt in fmts if isinstance(fmts, list) else [fmts]:
self.save_static(fmt, settings) # pass settings
except (AttributeError, ValueError) as e:
p = os.path.splitext(self.path)[0] + ".png"
if os.path.exists(p):
os.remove(p)
logging.warning("No static plots are saved due to an export problem:")
logging.warning(e)

elif self.fig:
# Legacy Matplotlib path
fmts = settings.get("format", ["png"])
dpi = int(settings.get("dpi", 300))
if isinstance(fmts, list):
for fmt in fmts:
self.fig.savefig(
fname=self.path,
format=settings["format"],
fname=self.path + "." + fmt,
format=fmt,
bbox_inches="tight",
dpi=dpi,
)
else:
sys.exit("No method to save plot object: no html or fig defined.")
self.fig.savefig(
fname=self.path,
format=fmts,
bbox_inches="tight",
dpi=dpi,
)
else:
sys.exit("No method to save plot object: no html or fig defined.")

def show(self):
if self.fig:
return self.fig.fig
return getattr(self.fig, "fig", self.fig)
else:
sys.stderr.write(".show not implemented for Plot instance without fig attribute!")

def save_static(self, figformat):
def save_static(self, figformat, settings):
"""
Export a Plotly figure using Plotly's image writer.
Export a Plotly figure as a static image with real DPI.
Prefers utils.write_static_image; falls back to explicit pixel size.
"""
output_path = self.path.replace(".html", f".{figformat}")
dpi = int(settings.get("dpi", 300))

if self.fig is None:
logging.warning("No figure attached to Plot; skipping static export for %s", output_path)
return

# JSON just dumps the figure spec
if figformat.lower() == "json":
try:
pio.write_json(self.fig, output_path)
logging.info("Saved %s as JSON", output_path)
except Exception as e:
logging.warning("Failed to write JSON for %s: %s", output_path, e)
return

# Preferred path: DPI-aware helper
try:
pio.write_image(self.fig, output_path, format=figformat)
logging.info(f"Saved {output_path} as {figformat}")
if write_static_image is not None:
write_static_image(self.fig, output_path, dpi=dpi)
logging.info("Saved %s as %s (dpi=%d)", output_path, figformat, dpi)
return
except Exception as e:
logging.warning("DPI helper failed for %s: %s; falling back to explicit px size", output_path, e)

# Hard fallback so we don't end up at 700x500 defaults
width_px = int(6.4 * dpi)
height_px = int(4.8 * dpi)
try:
pio.write_image(self.fig, output_path, width=width_px, height=height_px, scale=1)
logging.info("Fallback saved %s at %dx%d px", output_path, width_px, height_px)
except Exception as e:
logging.warning("No static plots are saved due to an export problem:")
logging.warning(e)

33 changes: 33 additions & 0 deletions scripts/agm_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#! /bin/bash

#SBATCH --time=04-00:00:00
#SBATCH --partition=defq
#SBATCH --mail-user=email@email.org
#SBATCH --mail-type=BEGIN,END,FAIL
#SBATCH --ntasks-per-node=64
#SBATCH --mem=128GB
#SBATCH --nodes=1
#SBATCH --job-name=nplot
#SBATCH --comment=nplot

source /home/tmhagm8/scratch/nanoplot_env/bin/activate

# Go to the repo root
cd /home/tmhagm8/scratch/NanoPlot

# Make sure to use right Python imports
export PYTHONPATH="$PWD:$PYTHONPATH"

# Double check imports
python - <<'PY'
import nanoplotter.plot as p
import nanoplot.utils as u
print("USING nanoplotter.plot:", p.__file__)
print("USING nanoplot.utils :", u.__file__)
PY

# check it
python -m nanoplot.NanoPlot \
--fastq /home/tmhagm8/scratch/SOMAteM_bckp/SOMAteM/examples/data/B011_2.fastq.gz \
-t 14 --verbose --minqual 4 --dpi 600 \
-o /home/tmhagm8/scratch/NanoPlot/scripts/agm_test -f png
2 changes: 2 additions & 0 deletions scripts/agm_tests/LengthvsQualityScatterPlot_dot.html

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions scripts/agm_tests/LengthvsQualityScatterPlot_kde.html

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading