I have a performance problem, and I would like to reach out to you to see if there is something simple I can do to speed up the process.
I have developped an annotation module in Slicer that works bug-free. Annotators segment lesions on CT images (512x512x198) and answer questions about the segments. I have created a personalized “save” feature that saves the non-overlapping segments into a single numpy array (encoded by the integer of the Segment number) and the questions and answers as a json all in a hdf5 file. Annotators open unfinished cases by selecting them in a QComboBox, which sets off a currentIndexChanged event connected to my load function onLoadHDF5. The function reads the hdf5, gets the segments from the numpy array, loads each one into a vtkMRMLLabelMapVolumeNode and then imports the labelmap into the segmentation node.
Below is a shortened version of the code I am using for this
def onLoadHDF5(self,hdf5_filename):
#Read hdf5 into memory
with h5py.File(self.local_hdf5_filename,"r") as gtFile:
gt_dicts = gtFile['dicts'][::]
gt_voxels = gtFile['voxels']
lesionOverlapArray = gt_voxels['SegmentationOverlap'][::]
#extract ground truth dictionaries
lsnDict = loads(gt_dicts[1])
#Fill in Number of Segments in List
for segmentName,segmentDict in lsnDict.items(): #segmentName = 'Lesion 1', #segmentDict is a dictionary containing questions and answers about the lesion
segmentIDNo = segmentName.split(' ')[1]
labelMapVolumeNode = slicer.vtkMRMLLabelMapVolumeNode()
labelMapVolumeNode.CopyOrientation(self.volumeNode) #self.volumeNode is the reference CT image(512x512x198)
segmentArray = (lesionOverlapArray == int(segmentIDNo)).astype(int)
slicer.util.updateVolumeFromArray(labelMapVolumeNode,segmentArray)
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelMapVolumeNode,self.segmentationNode)
#Code to load Segment Questions
Currently, I have a very complicated case with over 250 segments on one image, and it takes a very long time to load the segments into Slicer with the method I am using above (saving is similar too).
I recently timed the time it took to go through each loop in the for loop to load the segment into slicer. What I wasn’t expecting was that the time increased for each loop even though the code is the same. For example, the first loop took 0.91s, and by the last loop, it took 5.58s! This resulted in about 14 minutes of loading time for this particular case.
My questions are
Why does the time increase per loop? Is it a memory problem?
Since this is a function connected to an event, I can’t do anything else within Slicer, and the segments don’t load until the very end (and not one by one with each loop). Could this also induce memory problems (maybe the program stores everything into memory until the function finishes executing instead of “dumping” the segments into the segmentation node one by one)?
Thank you for any insights for this problem, and thanks for an amazing software!
Hi Andras,
Thanks for your answer. I’m not sure how to investigate memory leaks. After a quick Google search, I opened the program “resmon” in Windows 10 (link here). The orange curve should be the contribution of Slicer.
Throughout the loading process, it stayed around 8-12% CPU Usage and 57% Maximum Frequency (don’t know what that means), and 23% Used Physical Memory. However, I have no idea if this is how I’m supposed to inspect memory leaks. In fact, it just looks like Slicer is using almost all of my 12 CPUs (apparently 10% of each), but I don’t see any orange curve in the Memory section…
Memory usage is shown on the “Memory” tab. If numbers for SlicerApp-real.exe are constantly increasing then probably you are leaking memory (you don’t release unused memory).
Apologies. Perhaps I wasn’t clear. The above screenshot of my computer activity was taken while I was loading the complicated case into Slicer (a couple minute in, actually). I did not see the blue curve in the Memory section increase, and I didn’t see any orange curves (Slicer) in the Memory section. Moreover, the percent of used physical memory always stayed around 24%. The only changes I saw while loading the case into Slicer was an increase in the CPU usage. In the CPU part, the orange curve appears, indicating - I guess - that Slicer is using my CPUs to load the data, but the percent CPU usage stayed low around 10%. I validated this in the “Memory” tab too. The screenshot below was during loading as well.
Moreover, you can see that I still have a lot of free memory left. I have a total of 64G, so it should be plenty despite the large number of segmentations, but please correct me if I am wrong.
Does this mean that memory is not an issue?
Otherwise, is there a way to rewrite my function or connect the QComboBox such that, as each vtkMRMLLabelMapVolumeNode is pushed to the SegmentationNode in my for loop, I can “release the unused memory” and make the Segment appear in Slicer in real time (and not wait for the entire for loop to finish)? This might be a general Qt question than something specific to Slicer.
Thanks for all the information. Based on what you described, it does not seem to be a memory problem after all.
We have greatly improved performance of dealing with many segments in Slicer-4.11. So, if you are still using Slicer-4.10 then upgrade to latest Slicer Preview Release now.
Are your segments overlapping or you have a single 3D voxel array? If you have a single 3D array then you can import all the segments at once (using ImportLabelmapToSegmentationNode), and then update segment names of the already imported segments. This bulk import should be much faster than adding segments one by one.
Thanks for the tip! I didn’t realize I could upload a 3D array and have Slicer import the different labels as the different segments. It’s so simple - and yes, this worked like a charm.