Skip to content
Merged
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
95 changes: 78 additions & 17 deletions exact/util/cellvizio.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,36 +52,33 @@ def __init__(self,filename):
self.fi = fileinfo()
self.fileHandle = open(filename, 'rb');
self.fileHandle.seek(5) # we find the FPS at position 05
fFPSByte = self.fileHandle.read(4)
self.fps = struct.unpack('>f', fFPSByte)[0]


self.fileHandle.seek(10) # we find the image size at position 10
fSizeByte = self.fileHandle.read(4)
self.fi.size = int.from_bytes(fSizeByte, byteorder='big', signed=True)
self.fi.nImages=1000

self.fi.width = 576
if ((self.fi.size/(2*self.fi.width))%2!=0):
self.fi.width=512
self.fi.height=int(self.fi.size/(2*self.fi.width))
else:
self.fi.height=int(self.fi.size/(2*self.fi.width))

self.filestats = stat(self.fileName)
self.fi.nImages=1000
self.fi.nImages = int((self.filestats.st_size-self.fi.offset) / (self.fi.size+self.fi.gapBetweenImages))

self.numberOfFrames = self.fi.nImages


meta = self.getMostRelevantMetaInfo()
self.fi.width = int(meta.get('width', 0))
self.fi.height = int(meta.get('height', 0))
self.fps = float(meta.get('framerate', 0))

self.geometry_imsize = [self.fi.height, self.fi.width]
self.imsize = [self.fi.height, self.fi.width]
self.geometry_tilesize = [(self.fi.height, self.fi.width)]
self.geometry_rows = [1]
self.geometry_columns = [1]
self.levels = [1]
self.channels = 1
self.mpp_x = 250/576 # approximate number for gastroflex, 250 ym field of view, 576 px
self.mpp_y = 250/576

self.fovx = float(meta.get('fovx', 250))
self.fovy = float(meta.get('fovy', 250))
print(f"fovx: {self.fovx}, fovy: {self.fovy}")
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This print statement should be removed or replaced with proper logging. Use logger.debug() instead for debug output in production code.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, let's do this with the next commit.

self.mpp_x = self.fovx/self.fi.width # approximate number for gastroflex, 250 ym field of view, 576 px
self.mpp_y = self.fovy/self.fi.height
Comment on lines +79 to +80
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential division by zero if width or height metadata is missing or zero. The mpp_x and mpp_y calculations will fail if self.fi.width or self.fi.height is 0 (which would happen if the metadata keys are not found, since the default is 0). Add validation to ensure width and height are non-zero before performing these calculations, or provide sensible fallback values.

Copilot uses AI. Check for mistakes.


# generate circular mask for this file
self.circMask = circularMask(self.fi.width,self.fi.height, self.fi.width-2).mask
Expand All @@ -91,6 +88,52 @@ def __init__(self,filename):
openslide.PROPERTY_NAME_MPP_Y: self.mpp_y,
openslide.PROPERTY_NAME_OBJECTIVE_POWER:20,
openslide.PROPERTY_NAME_VENDOR: 'MKT'}


def getMetaInfo(self):
# the meta information at the MKT file always starts with "00616C6C 6F776564 5F656761 696E5F65 6F666673 65745F70 61697273 3D" or allowed_egain_eoffset_pairs= in the hex code
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment refers to "00616C6C 6F776564 5F656761 696E5F65 6F666673 65745F70 61697273 3D" as the hex representation of "allowed_egain_eoffset_pairs=", but this hex string representation is not actually used in the code and adds unnecessary complexity to the comment. The comment would be clearer if it simply stated that metadata starts with "allowed_egain_eoffset_pairs=".

Suggested change
# the meta information at the MKT file always starts with "00616C6C 6F776564 5F656761 696E5F65 6F666673 65745F70 61697273 3D" or allowed_egain_eoffset_pairs= in the hex code
# the meta information in the MKT file always starts with "allowed_egain_eoffset_pairs="

Copilot uses AI. Check for mistakes.
# we need to read the whole file to find the meta information
with open(self.fileName, 'rb') as f:
fileContent = f.read()
# find start position with fileContent.find(b'allowed_egain_eoffset_pairs=') that searches for the byte sequence
metaStart = fileContent.rfind(b'allowed_egain_eoffset_pairs=')
metaEnd = fileContent.find(b'<CEND', metaStart)
metaInfo = fileContent[metaStart:metaEnd].decode('utf-8')
return metaInfo


def getMostRelevantMetaInfo(self):
metaInfo = self.getMetaInfo()
relevantInfo = {}

for line in metaInfo.split('\n'):
if "=" not in line:
continue
key, value = line.split('=')
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The split logic is fragile and will fail if a line contains multiple '=' characters. For example, a line like "key=value=something" would be incorrectly parsed. Consider using split('=', 1) to split only on the first occurrence of '=', or use a more robust parsing approach.

Suggested change
key, value = line.split('=')
key, value = line.split('=', 1)

Copilot uses AI. Check for mistakes.
if key == 'framerate':
value_str = value.strip()
try:
framerate = float(value_str)
except ValueError:
print(f"Warning: invalid framerate value in metadata: {value_str!r}")
continue
if framerate <= 0:
print(f"Warning: non-positive framerate value in metadata: {framerate}")
continue
relevantInfo['framerate'] = value_str
relevantInfo['duration_seconds'] = self.fi.nImages / framerate
elif key == 'width':
relevantInfo['width'] = value
elif key == 'height':
relevantInfo['height'] = value
elif key == 'fovx':
relevantInfo['fovx'] = value
elif key == 'fovy':
relevantInfo['fovy'] = value
elif key == 'patient_id':
relevantInfo['patient_id'] = value
Comment on lines 112 to 134
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key from the split operation is not stripped of whitespace, which could cause comparison failures if there's any leading or trailing whitespace in the metadata. Consider using key.strip() when comparing against expected key names to make the parsing more robust.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally not a problem in MKT File format.


return relevantInfo

def _2_init__(self, filename):

Expand Down Expand Up @@ -137,7 +180,25 @@ def _2_init__(self, filename):

self.circMask = circularMask(self.fi.width,self.fi.height, self.fi.width-2).mask

@property
def meta_data(self) -> dict:
# Cache metadata to avoid repeated file I/O and parsing.
if not hasattr(self, "_meta_data_cache"):
self._meta_data_cache = self.getMostRelevantMetaInfo()
return self._meta_data_cache

@property
def meta_data_dict(self) -> dict:
meta_data_dict = {
'width': 'Image width (pixels)',
'height': 'Image height (pixels)',
'framerate': 'Frame rate (fps)',
'duration_seconds': 'Duration (seconds)',
'patient_id': 'Patient ID',
'fovx': 'Field of view x (micrometers)',
'fovy': 'Field of view y (micrometers)'
}
return meta_data_dict

@property
def seriesInstanceUID(self) -> str:
Expand Down