diff --git a/requirements.txt b/requirements.txt index 9a9b21721..2a7a6f2f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,7 +27,6 @@ pyyaml requests < 2.30 requests_unixsocket shapely -sparklines >= 0.4.2 # tensorflow versions might require different versions uvicorn uvicorn>=0.17.6 diff --git a/src/ocrd/processor/helpers.py b/src/ocrd/processor/helpers.py index 209fa5f67..f5b601063 100644 --- a/src/ocrd/processor/helpers.py +++ b/src/ocrd/processor/helpers.py @@ -11,7 +11,7 @@ from click import wrap_text from ocrd.workspace import Workspace -from ocrd_utils import freeze_args, getLogger, config, setOverrideLogLevel, getLevelName +from ocrd_utils import freeze_args, getLogger, config, setOverrideLogLevel, getLevelName, sparkline __all__ = [ @@ -106,7 +106,6 @@ def run_processor( if any(x in config.OCRD_PROFILE for x in ['RSS', 'PSS']): backend = 'psutil_pss' if 'PSS' in config.OCRD_PROFILE else 'psutil' from memory_profiler import memory_usage - from sparklines import sparklines try: mem_usage = memory_usage(proc=processor.process, # only run process once @@ -123,7 +122,7 @@ def run_processor( chdir(old_cwd) mem_usage_values = [mem for mem, _ in mem_usage] mem_output = 'memory consumption: ' - mem_output += ''.join(sparklines(mem_usage_values)) + mem_output += sparkline(mem_usage_values) mem_output += ' max: %.2f MiB min: %.2f MiB' % (max(mem_usage_values), min(mem_usage_values)) logProfile.info(mem_output) else: diff --git a/src/ocrd_utils/__init__.py b/src/ocrd_utils/__init__.py index 90cd55477..e566ce5ff 100644 --- a/src/ocrd_utils/__init__.py +++ b/src/ocrd_utils/__init__.py @@ -198,6 +198,7 @@ partition_list, parse_json_string_or_file, parse_json_string_with_comments, + sparkline, remove_non_path_from_url, safe_filename) diff --git a/src/ocrd_utils/constants.py b/src/ocrd_utils/constants.py index 0b9f0ae02..dc255bc1b 100644 --- a/src/ocrd_utils/constants.py +++ b/src/ocrd_utils/constants.py @@ -110,3 +110,24 @@ RESOURCE_LOCATIONS = ['data', 'cwd', 'system', 'module'] DEFAULT_METS_BASENAME = 'mets.xml' + + +# 2581 ▁ LOWER ONE EIGHTH BLOCK +# 2582 ▂ LOWER ONE QUARTER BLOCK +# 2583 ▃ LOWER THREE EIGHTHS BLOCK +# 2584 ▄ LOWER HALF BLOCK +# 2585 ▅ LOWER FIVE EIGHTHS BLOCK +# 2586 ▆ LOWER THREE QUARTERS BLOCK +# 2587 ▇ LOWER SEVEN EIGHTHS BLOCK +# 2588 █ FULL BLOCK +SPARKLINE_CHARS = [ + ' ', + '\u2581', + '\u2582', + '\u2583', + '\u2584', + '\u2585', + '\u2586', + '\u2587', + '\u2588', +] diff --git a/src/ocrd_utils/str.py b/src/ocrd_utils/str.py index f5b9242d3..406fcc975 100644 --- a/src/ocrd_utils/str.py +++ b/src/ocrd_utils/str.py @@ -4,7 +4,8 @@ import re import json -from .constants import REGEX_FILE_ID +from typing import List, Union +from .constants import REGEX_FILE_ID, SPARKLINE_CHARS from .deprecate import deprecation_warning from warnings import warn from numpy import array_split @@ -235,3 +236,18 @@ def partition_list(lst, chunks, chunk_index=None): if chunk_index is not None: return [ret[chunk_index]] return ret + +def sparkline(values : List[int]) -> str: + """ + Render a list of points with block characters + """ + if any(x is None or not isinstance(x, (int, float)) or x < 0 for x in values): + # return an empty string on non-positive-int values, better not to + # output a sparkline than to cancel execution due to problematic input + return '' + max_value = max(values) + max_mapping = len(SPARKLINE_CHARS) - 1 + # normalize to 0..1 and convert to index in SPARKLINE_CHARS + mapped = [int(x / max_value * max_mapping) for x in values] + return ''.join(SPARKLINE_CHARS[x] for x in mapped) + diff --git a/tests/test_utils.py b/tests/test_utils.py index d2093c465..8fe3fd373 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -21,6 +21,7 @@ is_string, membername, generate_range, + sparkline, nth_url_segment, remove_non_path_from_url, @@ -325,6 +326,12 @@ def test_partition_list(): assert partition_list(lst_13, chunks=4) == [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10], [11, 12, 13]] assert partition_list(lst_13, chunks=4, chunk_index=1) == [[5, 6, 7]] +def test_sparkline(): + assert sparkline([5, 2, 3]) == '█▃▄' + assert sparkline([1000, 1, 2222]) == '▃ █' + assert sparkline([8, 7, 6, 5, 4, 3, 2, 1, 0]) == '█▇▆▅▄▃▂▁ ' + assert sparkline([-1, None, 'forty-two']) == '' + if __name__ == '__main__': main(__file__)