"""
Working with STAR files
STAR files are CIF files.
To use STAR files with ``sfftk``, the user needs to provide at least:
- a STAR file
- a map file (MRC, REC, MAP, CCP4, etc.)
.. code-block:: bash
sff convert <starfile> [--subtomogram-average <mrcfile>] -o <outputfile> [other options]
The presence of the ``--star`` flag tells ``sfftk`` that what is about to be generated is a refinement model plus transforms.
"""
import numpy
import pathlib
import sfftkrw.schema.adapter_v0_8_0_dev1 as schema
from sfftkrw.core.print_tools import print_date
from .base import Segment, Segmentation
from ..formats import map as mapformat
from ..readers import starreader, mapreader
[docs]
class RelionStarSegment(Segment):
"""Class representing a Relion STAR file segment"""
def __init__(self, particles: starreader.StarTable, euler_angle_convention='ZYZ', degrees=True, verbose=False,
name="Particle refined using subtomogram averaging"):
self._particles = particles
self._euler_angle_convention = euler_angle_convention
self._degrees = degrees
self._verbose = verbose
self._name = name
[docs]
def convert(self, **kwargs):
"""Convert the segment to an EMDB-SFF segment"""
segment = schema.SFFSegment()
# metadata
segment.biological_annotation = schema.SFFBiologicalAnnotation()
segment.biological_annotation.name = self._name
segment.colour = schema.SFFRGBA(random_colour=True)
segment.shape_primitive_list = schema.SFFShapePrimitiveList()
if 'transforms' in kwargs:
transforms = kwargs['transforms']
else:
transforms = schema.SFFTransformList()
if self._verbose:
print_date(f"Using Euler angle convention: {self._euler_angle_convention}")
print_date(f"Euler angles in degrees: {not self._degrees}")
for particle in self._particles:
transform = schema.SFFTransformationMatrix.from_array(
particle.to_affine_transform(
axes=self._euler_angle_convention,
degrees=self._degrees
)
)
shape = schema.SFFSubtomogramAverage(
lattice_id=kwargs.get('lattice_id'),
value=1.0, # todo: capture the isosurface value e.g. from the CLI,
transform_id=transform.id,
)
segment.shape_primitive_list.append(shape)
transforms.append(transform)
return segment, transforms
[docs]
class RelionStarSegmentation(Segmentation):
"""Class that represents a Relion STAR file segmentation"""
def __init__(self, fn, particle_fn, euler_angle_convention='ZYZ', degrees=True, *_args, **_kwargs):
"""Initialise the segmentation"""
self._fn = fn
self._particle_fn = particle_fn
self._euler_angle_convention = euler_angle_convention
self._degrees = degrees
self._segmentation = starreader.get_data(self._fn, *_args, **_kwargs)
self._density = mapreader.get_data(self._particle_fn, *_args, **_kwargs)
self._segments = [
RelionStarSegment(
self._segmentation.tables['_rln'],
euler_angle_convention=self._euler_angle_convention,
degrees=self._degrees,
verbose=_kwargs.get('verbose', False),
name=pathlib.Path(self._fn).name
)]
@property
def header(self, ):
"""Return the header"""
return RelionStarHeader(self._density)
@property
def segments(self):
"""Return the segments"""
return self._segments
[docs]
def convert(self, name=None, software_version=None, processing_details=None, details=None, verbose=False,
transform=None):
"""Convert the segmentation to an EMDB-SFF segmentation"""
segmentation = schema.SFFSegmentation()
# metadata
segmentation.name = name if name is not None else "RELION Subtomogram Average"
segmentation.primary_descriptor = "shape_primitive_list"
segmentation.software_list = schema.SFFSoftwareList()
segmentation.software_list.append(
schema.SFFSoftware(
name='RELION',
version=software_version if software_version is not None else 'v4.0',
processing_details=processing_details
)
)
segmentation.details = details
# transforms
segmentation.transform_list = schema.SFFTransformList()
if transform is not None:
_transform = schema.SFFTransformationMatrix.from_array(transform)
segmentation.transform_list.append(
_transform
)
else:
_transform = schema.SFFTransformationMatrix.from_array(
numpy.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], ]))
segmentation.transform_list.append(
_transform
)
# lattice: we need to know the lattice because we reference it in the segment
segmentation.lattice_list = schema.SFFLatticeList()
lattice = schema.SFFLattice(
mode=self.header.mode,
endinaness=self.header.endianness,
size=schema.SFFVolumeStructure(
cols=self.header.cols,
rows=self.header.rows,
sections=self.header.sections
),
start=schema.SFFVolumeIndex(
cols=self.header.start_cols,
rows=self.header.start_rows,
sections=self.header.start_sections
),
data=self._density.voxels
)
segmentation.lattice_list.append(lattice)
segmentation.segment_list = schema.SFFSegmentList()
for segment in self.segments:
_segment, _transforms = segment.convert(lattice_id=lattice.id, transforms=segmentation.transform_list)
segmentation.segment_list.append(_segment)
segmentation.transform_list = _transforms
segmentation.details = details
return segmentation
[docs]
class RelionMultiStarSegmentation(Segmentation):
def __init__(self, fn_list, particle_fn, euler_angle_convention='ZYZ', degrees=True, *_args, **_kwargs):
"""Initialise the segmentation"""
self._fns = fn_list
self._particle_fn = particle_fn
self._euler_angle_convention = euler_angle_convention
self._degrees = degrees
self._segmentation = list()
for fn in self._fns:
self._segmentation.append(starreader.get_data(fn, *_args, **_kwargs))
self._density = mapreader.get_data(self._particle_fn, *_args, **_kwargs)
self._segments = list()
for index, _segmentation in enumerate(self._segmentation):
self._segments.append(
RelionStarSegment(
_segmentation.tables['_rln'],
euler_angle_convention=self._euler_angle_convention,
degrees=self._degrees,
verbose=_kwargs.get('verbose', False),
name=pathlib.Path(self._fns[index]).name
))
@property
def header(self, ):
"""Return the header"""
return RelionStarHeader(self._density)
@property
def segments(self):
"""Return the segments"""
return self._segments
[docs]
def convert(self, name=None, software_version=None, processing_details=None, details=None, verbose=False,
transform=None):
"""Convert the segmentation to an EMDB-SFF segmentation"""
segmentation = schema.SFFSegmentation()
# metadata
segmentation.name = name if name is not None else "RELION Subtomogram Average"
segmentation.primary_descriptor = "shape_primitive_list"
segmentation.software_list = schema.SFFSoftwareList()
segmentation.software_list.append(
schema.SFFSoftware(
name='RELION',
version=software_version if software_version is not None else 'v4.0',
processing_details=processing_details
)
)
segmentation.details = details
# transforms
segmentation.transform_list = schema.SFFTransformList()
if transform is not None:
_transform = schema.SFFTransformationMatrix.from_array(transform)
segmentation.transform_list.append(
_transform
)
else:
_transform = schema.SFFTransformationMatrix.from_array(
numpy.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], ]))
segmentation.transform_list.append(
_transform
)
# lattice: we need to know the lattice because we reference it in the segment
segmentation.lattice_list = schema.SFFLatticeList()
lattice = schema.SFFLattice(
mode=self.header.mode,
endinaness=self.header.endianness,
size=schema.SFFVolumeStructure(
cols=self.header.cols,
rows=self.header.rows,
sections=self.header.sections
),
start=schema.SFFVolumeIndex(
cols=self.header.start_cols,
rows=self.header.start_rows,
sections=self.header.start_sections
),
data=self._density.voxels
)
segmentation.lattice_list.append(lattice)
segmentation.segment_list = schema.SFFSegmentList()
for segment in self.segments:
_segment, _transforms = segment.convert(lattice_id=lattice.id, transforms=segmentation.transform_list)
segmentation.segment_list.append(_segment)
segmentation.transform_list = _transforms
segmentation.details = details
return segmentation