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

Feature/check minknow #351

Merged
merged 13 commits into from
May 7, 2024
Merged
100 changes: 100 additions & 0 deletions src/readfish/_compatibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""_compatibility.py

Contains utilities for checking readfish compatibility with various versions of MinKNOW.

Checks ranges of `readfish` against the `MinKNOW` version

Attributes:
LATEST_TESTED (str): The latest tested version of MinKNOW.
MINKNOW_COMPATIBILITY_RANGE (tuple): The compatibility range of MinKNOW versions for this version of readfish.
DIRECTION (Enum): An enumeration representing upgrade, downgrade, or no change directions.

"""

from minknow_api.manager import Manager
from packaging.version import parse as parse_version
from packaging.version import Version
from enum import Enum

LATEST_TESTED = "5.9.7"

# The versions of MinKNOW which this version of readfish can connect to
# Format - (lowest minknow version, highest version of minknow supported as an upper bound)
MINKNOW_COMPATIBILITY_RANGE = (
Version("5.0.0"),
Version(LATEST_TESTED),
)


class DIRECTION(Enum):
"""
Represents the direction in which the version of the readfish software should be changed
to be compatible with the tested version of an external tool (likely MinKNOW).

Attributes:
UPGRADE: Indicates that the readfish software version should be upgraded.
DOWNGRADE: Indicates that the readfish software version should be downgraded.
JUST_RIGHT: Indicates that the readfish software version is already compatible
with the tested version of the external tool.
"""

UPGRADE = "upgrade"
DOWNGRADE = "downgrade"
JUST_RIGHT = "do nothing"


def _get_minknow_version(host: str = "127.0.0.1", port: int = None) -> Version:
"""
Get the version of MinKNOW

:param host: The host the RPC is listening on, defaults to "127.0.0.1"
:param port: The port the RPC is listening on, defaults to None

:return: The version of MinKNOW readfish is connected to
"""
manager = Manager(host=host, port=port)
minknow_version = parse_version(manager.core_version)
return minknow_version


def check_compatibility(
comparator: Version,
version_range: tuple[Version, Version] = MINKNOW_COMPATIBILITY_RANGE,
) -> DIRECTION:
"""
Check the compatibility of a given software version, between a given range,
inclusive of the right edge.

:param comparator: Version of the provided software, for example MinKNOW 5.9.7
:param version_ranges: A tuple of lowest supported version, highest supported version

:return: A direction variant indicating if this version of readfish needs to be changed.

Examples:
>>> from packaging.version import Version
>>> check_compatibility(Version("5.9.5"), (Version("5.0.0"), Version("5.9.7")))
<DIRECTION.JUST_RIGHT: 'do nothing'>
>>> check_compatibility(Version("5.9.7"), (Version("5.0.0"), Version("5.9.7")))
<DIRECTION.JUST_RIGHT: 'do nothing'>
>>> check_compatibility(Version("5.9.8"), (Version("5.0.0"), Version("5.9.7")))
<DIRECTION.UPGRADE: 'upgrade'>
>>> check_compatibility(Version("4.9.0"), (Version("5.0.0"), Version("5.9.7")))
<DIRECTION.DOWNGRADE: 'downgrade'>
>>> if (action := check_compatibility(Version("6.0.0"), MINKNOW_COMPATIBILITY_RANGE)) in (
... DIRECTION.UPGRADE,
... DIRECTION.DOWNGRADE,
... ):
... action
<DIRECTION.UPGRADE: 'upgrade'>
"""
(
lowest_supported_version,
highest_supported_version,
) = version_range
if comparator < lowest_supported_version:
return DIRECTION.DOWNGRADE
return (
DIRECTION.JUST_RIGHT
if comparator <= highest_supported_version
else DIRECTION.UPGRADE
)
1 change: 1 addition & 0 deletions src/readfish/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from minknow_api.manager import Manager, FlowCellPosition
from minknow_api import Connection


if sys.version_info < (3, 11):
from exceptiongroup import BaseExceptionGroup

Expand Down
28 changes: 25 additions & 3 deletions src/readfish/entry_points/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@
from readfish._read_until_client import RUClient
from readfish._config import Action, Conf, make_decision, _Condition
from readfish._statistics import ReadfishStatistics
from readfish.__about__ import __version__
from readfish._compatibility import (
_get_minknow_version,
check_compatibility,
MINKNOW_COMPATIBILITY_RANGE,
DIRECTION,
)
from readfish._utils import (
get_device,
send_message,
Expand Down Expand Up @@ -485,6 +492,24 @@ def run(
# Setup logger used in this entry point, this one should be passed through
logger = logging.getLogger(f"readfish.{args.command}")

# Check MinKNOW version

minknow_version = _get_minknow_version(host=args.host, port=args.port)
if (
action := check_compatibility(minknow_version, MINKNOW_COMPATIBILITY_RANGE)
) in (
DIRECTION.UPGRADE,
DIRECTION.DOWNGRADE,
):
lower_bound, upper_bound = MINKNOW_COMPATIBILITY_RANGE
logger.warning(
f"""This readfish version ({__version__}) is tested for compatibility with MinKNOW v{lower_bound} to v{upper_bound}.
This version of minknow is {minknow_version}.
If readfish fails please try to {action.value} readfish.
If there isn't a newer version of readfish and readfish is failing, please open an issue:
https://github.com/LooseLab/readfish/issues"""
)

# Fetch sequencing device
position = get_device(args.device, host=args.host, port=args.port)

Expand Down Expand Up @@ -516,9 +541,6 @@ def run(

# start the client running
read_until_client.run(
# TODO: Set correct channel range
# first_channel=186,
# last_channel=187,
first_channel=1,
last_channel=read_until_client.channel_count,
max_unblock_read_length_seconds=args.max_unblock_read_length_seconds,
Expand Down
9 changes: 8 additions & 1 deletion src/readfish/entry_points/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,14 @@ def run(parser, args, extras) -> int:
try:
_ = conf.caller_settings.load_object("Caller")
except Exception as exc:
logger.error("Caller could not be initialised, see below for details")
logger.error("Caller could not be initialised.")
logger.error(
"Possible reasons for this include a mismatch between the ont basecaller client and the versions of guppy or dorado you are connecting to."
)
logger.error(
"Additional information is available here: https://looselab.github.io/readfish/FAQ.html#connection-error-bad-reply-could-not-interpret-message-from-server-for-request-load-config-reply-invalid-protocol"
)
logger.error("See below for further details on this specific error.")
logger.error(str(exc))
errors += 1
else:
Expand Down
27 changes: 19 additions & 8 deletions src/readfish/plugins/dorado.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from collections import namedtuple
from pathlib import Path
from typing import Iterable, TYPE_CHECKING
from packaging.version import parse as parse_version

import numpy as np
import numpy.typing as npt
Expand Down Expand Up @@ -66,16 +67,29 @@ def __init__(
self.supported_barcode_kits = None
self.supported_basecall_models = None
self.run_information = run_information
if sample_rate:
self.sample_rate = float(sample_rate)
else:
self.sample_rate = float(5000)
if self.run_information:
self.guppy_version = (
self.run_information.software_versions.guppy_connected_version
)

if parse_version(self.guppy_version) >= parse_version("7.3.9"):
logging.info(f"Connected to caller version {self.guppy_version}.")
else:
logging.info(
f"Trying to use minKNOW with a caller version {self.guppy_version}. If this is causing readfish to crash, try using a version of Dorado >= 7.3.9. You should also check for any updates available to readfish."
)

# Set our own priority
self.dorado_params = kwargs
self.dorado_params["priority"] = PyBasecallClient.high_priority
# Set our own client name to appear in the dorado server logs
self.dorado_params["client_name"] = "Readfish_connection"

if sample_rate:
self.sample_rate = float(sample_rate)
else:
self.sample_rate = float(5000)

self.validate()
self.caller = PyBasecallClient(**self.dorado_params)
self.caller.connect()
Expand Down Expand Up @@ -109,10 +123,7 @@ def validate(self) -> None:
raise RuntimeError(
f"The user account running readfish doesn't appear to have permissions to read the dorado base-caller socket. Please check permissions on {self.dorado_params['address']}. See https://github.com/LooseLab/readfish/issues/221#issuecomment-1375673490 for more information."
)
if not os.access(socket_path, os.W_OK):
raise RuntimeError(
f"The user account running readfish doesn't appear to have permissions to write to the dorado base-caller socket. Please check permissions on {self.dorado_params['address']}. See https://github.com/LooseLab/readfish/issues/221#issuecomment-1375673490 for more information."
)

# If we are connected to a live run, test if the base-caller model is acceptable.
# Connected to a live run via the minknow_api - get supported basecall and barcoding kits from the run info.
# Check them against provided values
Expand Down
16 changes: 16 additions & 0 deletions src/readfish/plugins/guppy.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from collections import namedtuple
from pathlib import Path
from typing import Iterable, TYPE_CHECKING
from packaging.version import parse as parse_version

import numpy as np
import numpy.typing as npt
Expand Down Expand Up @@ -62,8 +63,23 @@ def __init__(
self.supported_basecall_models = None
self.run_information = run_information

if self.run_information:
Adoni5 marked this conversation as resolved.
Show resolved Hide resolved
self.guppy_version = (
self.run_information.software_versions.guppy_connected_version
)

if parse_version(self.guppy_version) < parse_version("7.3.9"):
logging.info(f"Connected to caller version {self.guppy_version}.")
else:
logging.info(
f"Trying to connect to minKNOW with caller version {self.guppy_version}. This plugin requires a version of Dorado or Guppy < 7.3.9. If this is stopping readfish from running try changing [caller_settings.guppy] to [caller_settings.dorado]. You should also check for any updates available to readfish."
)

# Set our own priority
self.guppy_params = kwargs

# Remove the sample rate from the guppy params as it isn't required for the PyGuppyClient
self.guppy_params.pop("sample_rate", None)
self.guppy_params["priority"] = PyGuppyClient.high_priority
# Set our own client name to appear in the guppy server logs
self.guppy_params["client_name"] = "Readfish_connection"
Expand Down