TotalSegmentator icon indicating copy to clipboard operation
TotalSegmentator copied to clipboard

Memory Leak in actor.slicer

Open giuliabaldini opened this issue 2 years ago • 4 comments

Hi there,

thank you again for the nice work!

While using this, I have noticed a memory leak in lines 136-143 of preview.py, so when using actor.slicer.

You can reproduce this using the following code:

import tracemalloc
import linecache
from totalsegmentator.python_api import totalsegmentator

def filter_mem(snapshot):
    return snapshot.filter_traces((
        tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
        tracemalloc.Filter(False, "<frozen importlib._bootstrap_external>"),
        tracemalloc.Filter(False, "<unknown>"),
    ))

def display_top(snapshot, key_type='lineno', limit=10):
    top_stats = filter_mem(snapshot).statistics(key_type)
    unit = "MiB"
    if unit == "GiB":
        factor = 1.074e+9
    elif unit == "MiB":
        factor = 1.049e+6
    elif unit == "KiB":
        factor = 1024


    print("Top %s lines" % limit)
    for index, stat in enumerate(top_stats[:limit], 1):
        frame = stat.traceback[0]
        print("#%s: %s:%s: %.1f "
              % (index, frame.filename, frame.lineno, stat.size / factor) + unit)
        line = linecache.getline(frame.filename, frame.lineno).strip()
        if line:
            print('    %s' % line)

    other = top_stats[limit:]
    if other:
        size = sum(stat.size for stat in other)
        print(f"Number of other {len(other)}, size: {size / factor:.3f} {unit}")
    total = sum(stat.size for stat in top_stats)
    print(f"Total allocated size: {total / factor:.3f} {unit}")


if __name__ == "__main__":
    tracemalloc.start(30)

    presnapshot = tracemalloc.take_snapshot()
    totalsegmentator(
        input="/data/s1212.nii.gz",
        output="/data/totalseg",
        preview=True,
    )
    postsnapshot = tracemalloc.take_snapshot()

    for s in filter_mem(postsnapshot).compare_to(filter_mem(presnapshot), "lineno")[:10]:
        print(s)

    input("Press to stop")

Here is my output, as you see there is a lot of still occupied memory when the function is done:

/usr/local/lib/python3.8/dist-packages/nibabel/dataobj_images.py:373: size=123 MiB (+123 MiB), count=3 (+3), average=41.0 MiB
/usr/local/lib/python3.8/dist-packages/fury/actor.py:165: size=123 MiB (+123 MiB), count=3 (+3), average=41.0 MiB
...

If you remove those lines, the output is the following:

/usr/local/lib/python3.8/dist-packages/scipy/_lib/doccer.py:69: size=703 KiB (+703 KiB), count=225 (+225), average=3202 B
/usr/lib/python3.8/abc.py:85: size=689 KiB (+689 KiB), count=2514 (+2514), average=281 B
/usr/lib/python3.8/linecache.py:137: size=491 KiB (+491 KiB), count=4863 (+4863), average=103 B
/usr/lib/python3.8/inspect.py:2819: size=398 KiB (+398 KiB), count=5219 (+5219), average=78 B
/usr/local/lib/python3.8/dist-packages/matplotlib/_docstring.py:40: size=386 KiB (+386 KiB), count=122 (+122), average=3237 B
/usr/lib/python3.8/abc.py:102: size=369 KiB (+369 KiB), count=3483 (+3483), average=108 B
/usr/lib/python3.8/typing.py:764: size=329 KiB (+329 KiB), count=1891 (+1891), average=178 B
/usr/local/lib/python3.8/dist-packages/matplotlib/colors.py:1100: size=245 KiB (+245 KiB), count=6221 (+6221), average=40 B
/usr/local/lib/python3.8/dist-packages/attr/_make.py:301: size=229 KiB (+229 KiB), count=2360 (+2360), average=99 B
/usr/local/lib/python3.8/dist-packages/matplotlib/artist.py:140: size=220 KiB (+220 KiB), count=3516 (+3516), average=64 B
...

In my experience, after running the script multiple times, the usage of the program just keeps on increasing and the memory never gets released. For example, after one run with those lines:

CONTAINER ID   NAME       CPU %     MEM USAGE
6032e76bb495   totalseg   0.03%     2.951GiB

after two runs

CONTAINER ID   NAME       CPU %     MEM USAGE
6032e76bb495   totalseg   0.03%     2.969GiB

after 15 runs

CONTAINER ID   NAME       CPU %     MEM USAGE
6032e76bb495   totalseg   0.03%     6.295GiB

Thank you very much.

Best, Giulia

giuliabaldini avatar Aug 31 '23 15:08 giuliabaldini

Thanks for pointing this out. Unfortunately I am not very familiar with fury and vtk (those libraries provide the actor). I tried a few things but that did not change anything. For me the memory leak also does not increase over several runs.

wasserth avatar Sep 01 '23 08:09 wasserth

@wasserth how did you test it? I did something like this

presnapshot = tracemalloc.take_snapshot()
for _ in range(15):
    totalsegmentator(
        input="/data/s1212.nii.gz",
        output="/data/totalseg",
        preview=True,
    )
postsnapshot = tracemalloc.take_snapshot()

And do you also get a similar output for tracemalloc?

giuliabaldini avatar Sep 01 '23 08:09 giuliabaldini

I did the same and i get this output: Screenshot from 2023-09-01 11-27-08

wasserth avatar Sep 01 '23 09:09 wasserth

Have you also tried with a bigger image?

giuliabaldini avatar Sep 01 '23 09:09 giuliabaldini