neuro

package module
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 24, 2023 License: MIT Imports: 11 Imported by: 0

README

neuro

Go module for reading and writing structural neuroimaging file formats. Supports FreeSurfer MGH, MGZ, and related formats.

Main branch on Github Actions GoDoc license DOI

About

This repo contains a very early version of a Go module for reading structural neuroimaging file formats. Currently supported formats include:

  • FreeSurfer brain surface format: a triangular mesh file format. Used for recon-all output files like <subject>/surf/lh.white.
    • Read file format (function ReadFsSurface) into Mesh data structure.
    • Export Mesh to PLY, STL, OBJ formats.
    • Computation of basic Mesh properties (vertex and face count, bounding box, average edge length, total surface area, ...).
  • FreeSurfer curv format: stores per-vertex data (also known as a brain overlay), e.g., cortical thickness at each vertex of the brain mesh. Typically used for native space data for a single subject, for recon-all output files like <subject>/surf/lh.thickness.
    • Read file format (function ReadFsCurv)
    • Write file format (function WriteFsCurv)
    • Export data to JSON format.
  • FreeSurfer MGH and MGZ formats: store 3-dimensional or 4-dimensional (subject/time dimension) magnetic resonance imaging (MRI) scans of the human brain (e.g., <subject>/mri/brain.mgz). Can also be used to store per-vertex data, including multi-subject data on a common brain template like fsaverage (e.g., files like <subject>/surf/lh.thickness.fwhm5.fsaverage.mgh). The MGZ format is just gzip-compressed MGH format.
    • Read MGH format (function ReadFsMgh)
    • Read MGZ format (function ReadFsMgh), without the need to manually decompress first. The function handles both MGH and MGZ.
    • Full header information is available, so the image orientation can be reconstructed from the RAS information.
  • FreeSurfer label format: these files store labels, i.e., extra information for a subset of the vertices of a mesh or the voxels of a volume. Sometimes per-vertex or per-voxel data is stored in the labels data field, but in other case the relevant information is simply whether or not a certain element (voxel, vertex) is part of the label. Used for recon-all output files like <subject>/label/lh.cortex.label.
    • Read ASCII label format (function ReadFsLabel)
    • See also the related utility function VertexIsPartOfLabel

Vis

Usage

Installation
go get github.com/dfsp-spirit/neuro
Full Documentation including usage examples for functions

The full documentation can be found on the central go documentation page at pkg.go.dev.

It includes the full API documentation and usage examples for the functions.

Complete demo applications

Demo applications that use neuro are available in the cmd/ directory:

  • A command line app that reads a FreeSurfer mesh and prints some mesh information, like total surface area, average edgle length, etc: example_surface.go
  • A command line app that reads per-vertex cortical thickness data from a FreeSurfer curv file and exports it to a JSON file: example_curv.go
  • A command line app that reads a three-dimensional human brain scan (MRI image) from a FreeSurfer MGH file and prints some header data and the value of a voxel: example_mgh.go
  • A command line app that reads a label from a FreeSurfer surface label file and optionally exports the label data to JSON format: example_label.go

Developer information

Please see the Developer information if you want to compile and run the demo apps, unit tests, and similar things.

Author, License and Getting Help

The neuro module for Go was written by Tim Schäfer.

It is free software, published under the very permissive MIT license.

Note that this library is not a part of FreeSurfer, and it is in no way endorsed by the FreeSurfer developers. Please do not contact them regarding this library, especially not for support. Open an issue in this repo instead.

Documentation

Overview

Provides functions for reading and writing some structural neuroimaging file formats.

The neuro package is intended to be used in neuroimaging, with a focus on structural brain anatomy. Currently it provides functions to access fileformats used by FreeSurfer and some related neuroimaging software packages.

The package can read three-dimensional (3D) and 4D brain scans in MGH and MGZ format, typically produced from the raw DICOM files that are written by magnetic resonance imaging (MRI) hardware. Support for reading brain surface reconstructions (cortical meshes) and the related per-vertex data (like cortical thickness or sulcal depth at each point of the brain surface) are also included.

Index

Examples

Constants

View Source
const MRI_FLOAT int32 = 3

MRI data type representing a 32 bit float. Used by MGH format, see MghHeader struct.

View Source
const MRI_INT int32 = 1

MRI data type representing a 32 bit signed integer. Used by MGH format, see MghHeader struct.

View Source
const MRI_SHORT int32 = 4

MRI data type representing a 16 bit signed integer. Used by MGH format, see MghHeader struct.

View Source
const MRI_UCHAR int32 = 0

MRI data type representing an 8 bit unsigned integer. Used by MGH format, see MghHeader struct.

Variables

View Source
var Verbosity int = 0 // WARNING: If you increase Verbosity here and run the unit tests, the examples included with the tests will fail, because they expect a defined output on STDOUT, and increasing verbosity will produce extra output.

Verbosity is the verbosity level of the package. 0 = silent, 1 = info, 2 = debug.

Functions

func Export

func Export(mesh Mesh, filepath string, format string) (string, error)

Export exports a mesh to a file in the specified mesh file format.

Parameters:

  • mesh : the mesh to export
  • filepath : the filepath to export the mesh to
  • format : the mesh file format to use, one of 'obj' (for Wavefront Object Format), 'ply' (for Stanford PLY format), 'stl' (for StereoLithography format)

Returns

  • string : the mesh string representation in the requested format
  • error : the error if one occured, or nil otherwise

func MeshStats

func MeshStats(mesh Mesh) (map[string]float32, error)

Compute some basic mesh statistics.

Parameters:

  • mesh : the mesh to compute statistics for

Returns:

  • map[string]float32 : a map of statistics, with keys: 'numVertices' (number of vertices, interpret as int), 'numFaces' (number of faces, interpret as int), 'maxX', 'maxY', 'maxZ', 'minX', 'minY', 'minZ', 'meanX', 'meanY', 'meanZ', 'numEdges', 'avgEdgeLength', 'avgFaceArea', 'totalArea'.
Example (FromSurfaceFileVerts)
var surfFile string = "testdata/lh.white"
surf, _ := ReadFsSurface(surfFile)

stats, _ := MeshStats(surf)
fmt.Printf("Surface has %d vertices and %d faces.\n", int(stats["numVertices"]), int(stats["numFaces"]))
Output:

Surface has 149244 vertices and 298484 faces.

func NumFaces

func NumFaces(mesh Mesh) int

NumFaces computes the number of faces (aka polygons, or triangles) of a triangular mesh.

Parameters:

  • mesh : the mesh to compute the number of faces for

Returns:

  • int : the number of faces
Example
var mycube Mesh = GenerateCube()
nv := NumVertices(mycube)
nf := NumFaces(mycube)
fmt.Printf("Cube mesh has %d vertices and %d faces.\n", nv, nf)
Output:

Cube mesh has 8 vertices and 12 faces.

func NumVertices

func NumVertices(mesh Mesh) int

NumVertices computes the number of vertices of a triangular mesh.

Parameters:

  • mesh : the mesh to compute the number of vertices for

Returns:

  • int : the number of vertices
Example
var mycube Mesh = GenerateCube()
nv := NumVertices(mycube)
nf := NumFaces(mycube)
fmt.Printf("Cube mesh has %d vertices and %d faces.\n", nv, nf)
Output:

Cube mesh has 8 vertices and 12 faces.

func ReadFsCurv

func ReadFsCurv(filepath string) ([]float32, error)

Read a binary file in FreeSurfer curv format.

Curv files are used to store per-vertex descriptors like cortical thickness in native space (i.e., for a single subject, not mapped to a group template).

Parameters:

  • filepath: the path to the file, must be a FreeSurfer curv file from recon-all output, like subject/surf/lh.thickness.

Returns:

  • pervertex_data: float32 array of per-vertex descriptor values (e.g. cortical thickness)
  • error: an error if one occurred
Example
var curvFile string = "testdata/lh.thickness"

// Read the curv file
pvdata, _ := ReadFsCurv(curvFile)

fmt.Printf("Read %d values from curv file '%s'.\n", len(pvdata), curvFile)
Output:

Read 149244 values from curv file 'testdata/lh.thickness'.

func ToObjFormat

func ToObjFormat(mesh Mesh) (string, error)

Convert a mesh to OBJ format.

Parameters:

  • mesh : the mesh to convert

Returns:

  • string : the mesh string representation in OBJ format
  • error : the error if one occured, or nil otherwise
Example
var mycube Mesh = GenerateCube()
obj_str, err := ToObjFormat(mycube)
if err != nil {
	fmt.Printf("Error getting OBJ representation: %s\n", err)
}
fmt.Printf("OBJ format string has %d lines.\n", strings.Count(obj_str, "\n"))
Output:

OBJ format string has 21 lines.

func ToPlyFormat

func ToPlyFormat(mesh Mesh) (string, error)

Convert a mesh to PLY format.

Parameters:

  • mesh : the mesh to convert

Returns:

  • string : the mesh string representation in PLY format
  • error : the error if one occured, or nil otherwise
Example
var mycube Mesh = GenerateCube()
ply_str, err := ToPlyFormat(mycube)
if err != nil {
	fmt.Printf("Error getting PLY representation: %s\n", err)
}
fmt.Printf("PLY format string has %d lines.\n", strings.Count(ply_str, "\n"))
Output:

PLY format string has 30 lines.

func ToStlFormat

func ToStlFormat(mesh Mesh) (string, error)

Convert a mesh to STL format.

Parameters:

  • mesh : the mesh to convert

Returns:

  • string : the mesh string representation in STL format
  • error : the error if one occured, or nil otherwise
Example
var mycube Mesh = GenerateCube()
stl_str, err := ToStlFormat(mycube)
if err != nil {
	fmt.Printf("Error getting STL representation: %s\n", err)
}
fmt.Printf("STL format string has %d lines.\n", strings.Count(stl_str, "\n"))
Output:

STL format string has 86 lines.

func VertexIsPartOfLabel added in v0.1.2

func VertexIsPartOfLabel(label FsLabel, meshNumVertices int32) ([]bool, error)

Check for all vertices in the mesh whether they are part of the label.

Parameters:

  • label: the label to check
  • meshNumVertices: the number of vertices in the mesh

Returns:

  • is_part_of_label: a bool array of length meshNumVertices, where each element is true if the vertex is part of the label, and false otherwise.
  • error: an error if one occurred, e.g., the number of vertices in the mesh is less than the number of elements in the label.

func WriteFsCurv

func WriteFsCurv(filename string, data []float32) error

WriteFsCurv writes a FreeSurfer curv file.

Parameters:

  • filename: the name of the file to write. Path to it must exist.
  • data: the slice of float32 values. Must not be empty.

Returns:

  • error: an error if one occurred, e.g., the slice was empty. Or nil otherwise.

Types

type FsLabel added in v0.1.2

type FsLabel struct {
	ElementIndex []int32   // The index of the vertex or voxel in the volume or mesh. The first element is 0.
	CoordX       []float32 // The first coordinate of the vertex or voxel in the volume or mesh.
	CoordY       []float32 // The first coordinate of the vertex or voxel in the volume or mesh.
	CoordZ       []float32 // The first coordinate of the vertex or voxel in the volume or mesh.
	Value        []float32 // The per-element data.
}

Struct modelling a FreeSurfer label. A label contains information on a subset of the voxels or vertices only, i.e., the number of entries is typically less than the number of voxels or vertices in the volume or mesh. Sometimes per-vertex or per-voxel data is stored in the labels data field, but sometimes the relevant information is simply whether or not a certain element (voxel, vertex) is part of the label (e.g., for a cortex label), and the per-element data stored is not relevant (and typically set to 0.0).

func ReadFsLabel added in v0.1.2

func ReadFsLabel(filepath string) (FsLabel, error)

Read an file in FreeSurfer label format.

A label file is a text file representing vertices or voxels in a label. A label contains information on a subset of the voxels or vertices only, i.e., the number of entries is typically less than the number of voxels or vertices in the volume or mesh. Sometimes per-vertex or per-voxel data is stored in the labels data field, but sometimes the real information is whether or not a certain element (voxel, vertex) is part of the label (e.g., for a cortex label), and the per-element data stored is not relevant.

Parameters:

  • filepath: the path to the file, must be a FreeSurfer label file from recon-all output, like subject/label/lh.cortex.label.

Returns:

  • pervertex_data: float32 array of per-vertex descriptor values (e.g. cortical thickness)
  • error: an error if one occurred
Example
var labelFile string = "testdata/lh.cortex.label"

// Read the curv file
label, _ := ReadFsLabel(labelFile)

fmt.Printf("Read label containing %d vertices from label file '%s'.\n", len(label.ElementIndex), labelFile)
Output:

Read label containing 140891 vertices from label file 'testdata/lh.cortex.label'.

type Mesh

type Mesh struct {
	Vertices []float32
	Faces    []int32
}

Mesh is a struct that holds a triangular mesh, with vertices and faces. Faces are stored in vertex index representation.

Fields:

  • Vertices : the vertices of the mesh, as a slice of float32 values. The vertices are stored as a flat array of 3D coordinates, i.e. [x1, y1, z1, x2, y2, z2, ...]
  • Faces : the faces (a.k.a polygons or triangles) of the mesh, as a slice of int32 values. The faces are stored as a flat array of vertex indices, i.e. [v1, v2, v3, v1, v2, v3, ...]
Example
var mycube Mesh = GenerateCube()
nv := NumVertices(mycube)
nf := NumFaces(mycube)
fmt.Printf("Cube mesh has %d vertices and %d faces.\n", nv, nf)
Output:

Cube mesh has 8 vertices and 12 faces.
Example (FromData)
mesh := Mesh{}
mesh.Vertices = []float32{0.0, 1.0, 2.0, 3.0, 4.0, 5.0} // 2 vertices, 3 dimensions each
mesh.Faces = []int32{0, 1, 2, 3, 4, 5}                  // 2 faces, 3 vertices each
nv := NumVertices(mesh)
nf := NumFaces(mesh)
fmt.Printf("Mesh has %d vertices and %d faces.\n", nv, nf)
Output:

Mesh has 2 vertices and 2 faces.
Example (FromSurfaceFile)
var surfFile string = "testdata/lh.white"
surf, _ := ReadFsSurface(surfFile)

nv := NumVertices(surf)
nf := NumFaces(surf)
fmt.Printf("Surface has %d vertices and %d faces.\n", nv, nf)
Output:

Surface has 149244 vertices and 298484 faces.

func GenerateCube

func GenerateCube() Mesh

GenerateCube creates and returns a Mesh representing a cube.

This is mainly used in the examples and documentation.

Returns:

  • Mesh : the cube mesh
Example
var mycube Mesh = GenerateCube()
fmt.Printf("Cube mesh has %d vertices and %d faces.\n", NumVertices(mycube), NumFaces(mycube))
Output:

Cube mesh has 8 vertices and 12 faces.

func GenerateSphere added in v0.1.1

func GenerateSphere(radius float32, slices int, stacks int) Mesh

GenerateSphere creates and returns a Mesh representing a sphere.

This is mainly used in the examples and documentation.

Parameters:

  • radius : the radius of the sphere
  • slices : the number of slices (horizontal divisions)
  • stacks : the number of stacks (vertical divisions)

Returns:

  • Mesh : the sphere mesh

func ReadFsSurface

func ReadFsSurface(filepath string) (Mesh, error)

ReadFsSurface reads a FreeSurfer surface file and returns a Mesh struct.

A surface file is a binary file containing the reconstructed surface of a brain hemisphere.

Parameters:

  • filepath: path to the FreeSurfer mesh file, e.g. '<subject>/surf/lh.white'

Returns:

  • Mesh: a Mesh struct containing the mesh data
  • error: an error if one occurred
Example
var surfaceFile string = "testdata/lh.white"

// Read the curv file
mesh, _ := ReadFsSurface(surfaceFile)

fmt.Printf("Read mesh with %d vertices and %d faces from surface file '%s'.\n", len(mesh.Vertices)/3, len(mesh.Faces)/3, surfaceFile)
Output:

Read mesh with 149244 vertices and 298484 faces from surface file 'testdata/lh.white'.

type Mgh

type Mgh struct {
	Header MghHeader
	Data   MghData
}

Mgh models a full MGH format file, including the MghHeader and the MghData. See the separate documention for MghHeader and MghData for details on accessing fields.

func ReadFsMgh

func ReadFsMgh(filepath string, isGzipped string) (Mgh, error)

ReadFsMgh reads a FreeSurfer MGH file and returns it as an Mgh struct. The Mgh struct contains the MghHeader and MghData.

See the documentation for Mgh, MghHeader and MghData for details on accessing fields.

Parameters:

  • filepath: path to the FreeSurfer MGH file, e.g. '<subject>/mri/brain.mgh'. Note that gzipped MGH files (file extension .mgz) are currently not supported.
  • isGzipped: Whether to treat the file as gzip-compressed. If "auto", the file extension is used to determine whether the file is gzip-compressed. If not "auto", it has to be "yes"/"mgz" or "no"/"mgh" to force MGZ or MGH format, respectively.

Returns:

  • Mgh: an Mgh struct containing the MghHeader and MghData
Example (Tensor)
// Illustrates how to read the 4D MRI image returned by ReadFsMgh into a tensor data structure
// from the "gorgonia.org/tensor" package for convenient access to voxel values.
// This example requires 'import "gorgonia.org/tensor"'.
var mgzFile string = "testdata/brain.mgz"

mgh, _ := ReadFsMgh(mgzFile, "yes")
var h MghHeader = mgh.Header

data := tensor.New(tensor.WithShape(int(h.Dim1Length), int(h.Dim2Length), int(h.Dim3Length), int(h.Dim4Length)), tensor.WithBacking(mgh.Data.DataMriUchar))
val1, _ := data.At(99, 99, 99, 0)    // 77
val2, _ := data.At(109, 109, 109, 0) // 71
val3, _ := data.At(0, 0, 0, 0)       // 0

// values known from external tests with FreeSurfer software, try on command line: mri_info --voxel 99 99 99 testdata/brain.mgz

fmt.Printf("Voxel values=%d, %d, %d", val1, val2, val3)
Output:

Voxel values=77, 71, 0

type MghData

type MghData struct {
	DataMriUchar []uint8   // The data, if MghDataType is MRI_UCHAR. Otherwise this field contains random data.
	DataMriInt   []int32   // The data, if MghDataType is MRI_INT. Otherwise this field contains random data.
	DataMriFloat []float32 // The data, if MghDataType is MRI_FLOAT. Otherwise this field contains random data.
	DataMriShort []int16   // The data, if MghDataType is MRI_SHORT. Otherwise this field contains random data.
	MghDataType  int32     // The MRI data type code. See MRI_UCHAR, MRI_INT, MRI_FLOAT, MRI_SHORT. Use this to determine which of the data fields above is valid.
}

Struct modelling the data part of an MGH file. Only the data in the field identified by MghDataType is valid.

func ReadFsMghData

func ReadFsMghData(filepath string, hdr MghHeader, isGzipped string) (MghData, error)

ReadFsMghData reads the data part of an MGH or MGZ format file into an MghData struct.

See the documentation for MghData for details on accessing fields.

Parameters:

  • filepath: path to readable input file in MGH or MGZ format
  • hdr: MghHeader struct containing the header data
  • isGzipped: string indicating whether the input file is gzipped or not. If set to 'auto', the function will try to determine this automatically.

Returns:

  • MghData: an MghData struct containing the data
  • error: an error if one occurred, nil otherwise

type MghHeader

type MghHeader struct {
	MghVersion  int32 // version of the MGH file format. Currently, this is always 1.
	Dim1Length  int32 // number of voxels in x direction
	Dim2Length  int32 // number of voxels in y direction
	Dim3Length  int32 // number of voxels in z direction
	Dim4Length  int32 // number of voxels in 4th dimension (typically time or subject index)
	MghDataType int32 // MRI data type code. See MRI_UCHAR, MRI_INT, MRI_FLOAT, MRI_SHORT constants in this package.
	DoF         int32
	RasGoodFlag int16 // flag (1=yes, everything else=no) indicating whether the file contains valid RAS info.

	// All fields below are so-called RAS info fields. They should be ignored (assumed to contain random data) unless RasGoodFlag is 1.
	XSize float32 // size of voxels in x direction (mm)
	YSize float32 // size of voxels in y direction (mm)
	ZSize float32 // size of voxels in z direction (mm)

	Mdc    [9]float32 // 9 float values, the 3x3 Mdc matrix that contains image orientation information. The interpretation order is row wise (row1-column1, row1-column2, row1-column3, row2-column1, ...)
	Pxyz_c [3]float32 // 3 float values, the xyz coordinates of the central voxel. Think of the name 'Pxyz_c' as 'Point (x,y,z) coordinates of the center'.

	// There are 194 more (currently unused) bytes reserved for the header before the data part starts.
	Reserved [194]uint8 // Reached end of header after 284 bytes. Data in here should be considered random.
}

MghHeader models the header section of an MGH file. MGH stands for Massachusetts General Hospital, and the MGH format is a binary format for storing 3-dimensional or 4-dimensional structural MRI images of the human brain. The MGZ file extension is used for GZIP-compressed files in MGH format.

func ReadFsMghHeader

func ReadFsMghHeader(filepath string, isGzipped string) (MghHeader, error)

ReadFsMghHeader reads a FreeSurfer MGH file and returns the header as an MghHeader struct.

See the documentation of the MghHeader struct for details on the header fields.

Parameters:

  • filepath: path to the FreeSurfer MGH file, e.g. '<subject>/mri/brain.mgh'. Note that gzipped MGH files (file extension .mgz) are currently not supported.
  • isGzipped: Whether to treat the file as gzip-compressed. If "auto", the file extension is used to determine whether the file is gzip-compressed. If not "auto", it has to be "yes"/"mgz" or "no"/"mgh" to force MGZ or MGH format, respectively.

Returns:

  • MghHeader: an MghHeader struct containing the header data
  • error: an error if one occurred

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL