Skip to content

Commit

Permalink
Big rename for public package release
Browse files Browse the repository at this point in the history
* Rename to MagneticResonanceSignals to be clearer + new UUID to allow
  the package to temporarily co-exist with the old one.
* Rework the README, now that some goals seem clearer.
  • Loading branch information
c42f committed Jan 24, 2020
1 parent c4b076b commit b074286
Show file tree
Hide file tree
Showing 19 changed files with 88 additions and 106 deletions.
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2018, TRI, All Rights Reserved

The TriMRS.jl package contains code developed at TRI for magnetic resonance
The MagneticResonanceSignals.jl package contains code developed at TRI for magnetic resonance
spectroscopy.
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = "TriMRS"
uuid = "9da901aa-ab15-53db-a151-0fdc1ff1ff41"
name = "MagneticResonanceSignals"
uuid = "78eec104-8e80-495a-a1ef-01a2a2eeabab"
version = "0.1.0"

[deps]
Expand Down
119 changes: 51 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
# TriMRS
# MagneticResonanceSignals

Analysis tools for Magnetic Resonance Spectroscopy data, similar to the python
library [suspect](https://github.com/openmrslab/suspect). In scope, for now,
are:
File IO and signal processing tools for raw Magnetic Resonance data, with a
focus on spectroscopy.

* File input and output
* Basic signal processing
* Classifiers and feature engineering
This library was developed with the intent to flexibly process data from
unusual and bespoke magnetic resonance sequences. Similar to the python
library [suspect](https://github.com/openmrslab/suspect), the focus has been on
spectroscopy, so the higher level functionality is specific to spectroscopy.

While we initially rely on some of the suspect functionality, `TriMRS` includes
tooling specific to what we're working on at TRI (particularly 2D MRS) and is
also written in julia which is much nicer for this kind of numerical work.
However, rather than being limited to spectroscopy, this library tries to
provide rich and general access to the raw data in a way which reflects an
arbitrary MR experiment. In particular, it provides a reader for the raw
Siemens "twix" format and some nascent facilities to abstract over that format
with the capabilities and limitations of the physical experiment in mind.

## Quick start
In a way, this makes the library partly comparable to the much larger project
[ISMRMRD](https://ismrmrd.github.io/) and we should arguably have a reader for
ISMRMRD format here. However, ISMRMRD is an extra layer of conversion away from
the raw data and is somewhat imaging-focused (this is both good and bad).

Here's an example of how you can load COSY data from Siemens TWIX format,
## Installation

```
julia> using Pkg
julia> Pkg.clone("https://github.com/TRIImaging/MagneticResonanceSignals.jl.git")
```

## Spectroscopy processing quick start

Here's an example of how you can load L-COSY data from Siemens TWIX format,
convert it into a spectrum and view that spectrum:

```julia
using TriMRS, AxisArrays, Unitful
using MagneticResonanceSignals, AxisArrays, Unitful

cosy = mr_load("meas_MID00417_FID85233_svs_lcosy.dat")

Expand All @@ -43,7 +57,7 @@ contour(f2, reverse(f1), Matrix(transpose(log.(abs.(spec)))); levels=-10:0.3:-3,
Here's an example of how you can convert to Felix format:

```julia
using TriMRS, AxisArrays
using MagneticResonanceSignals, AxisArrays

cosy = mr_load("meas_MID00417_FID85233_svs_lcosy.dat")
signal = simple_averaging(cosy, downsample=2)
Expand Down Expand Up @@ -73,59 +87,28 @@ signal = zeropad(signal, Axis{:time1}, 4)
spec = spectrum(signal)
```

## Installation
## File IO

Raw data:

* `load_twix` — load Siemens raw scanner `"meas_*.dat"` "twix" format, as
produced by the twix exporter program available at clinical sites with a
Siemens IDEA licence.

* `mr_load` — load raw scanner data and recognize the sequence, wrapping in
sequence-specific metadata.

Spectroscopy:

* `load_rda`, `save_rda` — simple support for the Siemens ".rda" processed
spectroscopy format.
* `save_nmrpipe` — save spectroscopy data to
[NMRPipe](https://www.ibbr.umd.edu/nmrpipe/) format. We have found this
helpful for importing into mestrelab's
[MNova](https://mestrelab.com/software/mnova/) NMR processing software.
* `save_felix` — save spectroscopy data to [Felix NMR](http://www.felixnmr.com/)
".dat" format.

To use `TriMRS`, you'll need to have a version of python with the suspect
library installed. I suggest:
1. Download the anaconda python distribution
2. Create a new python environment called "julia"
3. Install suspect using `pip install suspect`
4. Activate the julia environment with something like `conda activate julia`,
and precompile `PyCall` from julia within this environment, using
`Pkg.add("PyCall")`

While you're at it, you should probably make sure `PyPlot.jl` will work nicely
in your python environment using `conda install matplotlib`.


## TriMRS Tools and Tool Packaging

Speculative plan:

Individual tools for end users reside in subdirectories of the `run` directory.
`TriMRS` contains some simple packaging scripts which can package pure julia
code into a zip file which can be distributed to users.

### Tool source code

* Tools reside in `run/toolname` directories which include:
- `main.jl` which is the main entry point script
- `REQUIRE` which defines package requirements as usual

### Tool packaging / package structure

* Running `tools/makepackage.jl tooldir` should create a zip file containig the
package structure, including
- A launcher of some kind which can find julia.exe and run the script. This
can also set an environment variable or command line argument to provide
some other indication that the script is being run in release mode.
- A VERSION file, specifying the TriMRS git version and some record of the
build process. Also enure that the git versions of packages are recorded
somewhere.

For installation, there's two possible implementations:
1. We recursively harvest all the required julia packages and distribute them
in a zip file. At packaging time, this requires using the union of REQUIRE
files from TriMRS and the tool, setting `JULIA_PKGDIR` to a clean scratch
dir, and zipping up all the code which appears there. At runtime we can
then set `JULIA_PKGDIR` to pick these up. If there's no binary
dependencies, this is a sure way to keep library dependencies under control.
For binary dependencies, we might run into big problems. We'd need to
ensure the package dir is portable and relocatable enough.
2. Ship a `REQUIRE` file and rely on `Pkg` to resolve this on the user machine
at install time (get launcher to autodetect whether an install step is
required?). The `REQUIRE` file should be "cooked" to exactly pin down the
package versions - `DeclarativePackages` style. Undoubtably this will cause
extra woes at install time, but it would remove some binary dependency
problems on different platforms.
## Self-contained processing tools

Pre-packaged tools for various processing tasks reside in the `run` directory.
4 changes: 2 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ build_script:
# Need to convert from shallow to complete for Pkg.clone to work
- IF EXIST .git\shallow (git fetch --unshallow)
- C:\projects\julia\bin\julia -e "versioninfo();
Pkg.clone(pwd(), \"TriMRS\"); Pkg.build(\"TriMRS\")"
Pkg.clone(pwd(), \"MagneticResonanceSignals\"); Pkg.build(\"MagneticResonanceSignals\")"

test_script:
- C:\projects\julia\bin\julia -e "Pkg.test(\"TriMRS\")"
- C:\projects\julia\bin\julia -e "Pkg.test(\"MagneticResonanceSignals\")"
2 changes: 1 addition & 1 deletion run/megapress_postproc/REQUIRE
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
ArgParse
TriMRS
MagneticResonanceSignals
2 changes: 1 addition & 1 deletion run/megapress_postproc/main.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using ArgParse
using TriMRS
using MagneticResonanceSignals

function rda_diff(in1file, in2file, outfile; anonymize=true)
in1header,in1data = load_rda(in1file)
Expand Down
6 changes: 3 additions & 3 deletions run/strip_twix_adjustment.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using TriMRS
using MagneticResonanceSignals
using StaticArrays

function strip_twix_adjustment(in_file, out_file)
open(in_file, "r") do src
open(out_file, "w") do dest
twix_id, num_measurements, mdata = TriMRS.read_meas_headers(src)
twix_id, num_measurements, mdata = MagneticResonanceSignals.read_meas_headers(src)

# Create temp buffer
buf = IOBuffer()
Expand Down Expand Up @@ -41,7 +41,7 @@ using ArgParse
argdef = ArgParseSettings(exc_handler=isinteractive() ? ArgParse.debug_handler : ArgParse.default_handler,
prog="Twix Adjustment Remover",
description="""
Small program to remove adjustment data on Siemens twix, which relies heavily on TriMRS.
Small program to remove adjustment data on Siemens twix, which relies heavily on MagneticResonanceSignals.
This program will take the in_file and write the no adjustment twix in out_file.
""")
@add_arg_table argdef begin
Expand Down
2 changes: 1 addition & 1 deletion run/twix_to_felix.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env julia

using TriMRS
using MagneticResonanceSignals
using Statistics
using Unitful
using AxisArrays
Expand Down
4 changes: 2 additions & 2 deletions run/twixheaders.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ end

parsed_args = parse_args(ARGS, argdef)

using TriMRS
using MagneticResonanceSignals
out_dir = parsed_args["out_dir"]
if !isdir(out_dir)
mkpath(out_dir)
end
TriMRS.dump_twix_headers(parsed_args["in_file"], out_dir)
MagneticResonanceSignals.dump_twix_headers(parsed_args["in_file"], out_dir)
2 changes: 1 addition & 1 deletion scripts/rda_diff.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

using DataStructures

# Copied from TriMRS
# Copied from MagneticResonanceSignals
"""
load_rda(rda_file)
Expand Down
6 changes: 2 additions & 4 deletions src/TriMRS.jl → src/MagneticResonanceSignals.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module TriMRS
module MagneticResonanceSignals

using StaticArrays
using DataStructures
Expand Down Expand Up @@ -88,12 +88,10 @@ export
export
felix_colors

@deprecate load_twix_raw load_twix

const _local_basefactors = Unitful.basefactors

function __init__()
Unitful.register(TriMRS)
Unitful.register(MagneticResonanceSignals)
merge!(Unitful.basefactors, _local_basefactors)
end

Expand Down
2 changes: 1 addition & 1 deletion src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

@unit ppm "ppm" PartsPerMillion 1//1000000 false

Unitful.register(TriMRS)
Unitful.register(MagneticResonanceSignals)

"""
4.7 is a chosen nominal chemical shift of water relative to TMS. Note that
Expand Down
5 changes: 3 additions & 2 deletions src/io_nmrpipe.jl
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,14 @@ end
"""
save_nmrpipe(io, signal, axes; frequency, ref_freq_offset)
Convert twix into NMR Pipe format to be loaded into third party analytical software, such as mestrenova. This currently supports up to 2 dimension.
Convert twix into NMR Pipe format to be loaded into third party analytical
software, such as MNova. This currently supports up to 2 dimension.
At minimal, for each dimension we should have:
- Signal data
- Axes
- Observation frequency (frequency)
- Carier frequency (ref_freq_offset)
- Carrier frequency (`ref_freq_offset`)
`ref_freq_offset` is a tuple containing the relative frequency offset between
obs_freq and frequency which will be set to 0 on the ppm scale.
Expand Down
2 changes: 1 addition & 1 deletion src/io_twix.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct LoopCounters <: FieldVector{14, UInt16}
ide::UInt16
end

loop_counter_index(name) = findfirst(isequal(name), fieldnames(TriMRS.LoopCounters))::Int
loop_counter_index(name) = findfirst(isequal(name), fieldnames(MagneticResonanceSignals.LoopCounters))::Int


"""
Expand Down
2 changes: 1 addition & 1 deletion test/fixedstring.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using TriMRS: FixedString
using MagneticResonanceSignals: FixedString

@testset "FixedString" begin
# Constructor
Expand Down
4 changes: 2 additions & 2 deletions test/io_nmrpipe.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using Unitful, TriMRS, Test
using TriMRS: FixedString
using Unitful, MagneticResonanceSignals, Test
using MagneticResonanceSignals: FixedString
using AxisArrays

@testset "NMRPipe format IO" begin
Expand Down
10 changes: 5 additions & 5 deletions test/io_twix.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,21 +86,21 @@ end
@testset "twix quality control" begin
twix = @test_logs (Logging.Warn,r"Unexpected empty meas packet") #=
=# load_twix("twix/sub-SiemensBrainPhantom_seq-svslcosy_incomplete.twix")
@test TriMRS.AcquisitionsIncomplete in twix.quality_control
@test MagneticResonanceSignals.AcquisitionsIncomplete in twix.quality_control

valid_twix_bytes = read("twix/sub-SiemensBrainPhantom_seq-svslcosy_inc-1.twix")

partially_zeroed_twix = copy(valid_twix_bytes)
partially_zeroed_twix[0x69:end] .= 0
twix = @test_logs (Logging.Warn,r"Unexpected empty measurement header sections") match_mode=:any #=
=# load_twix(IOBuffer(partially_zeroed_twix))
@test TriMRS.MeasHeaderEmpty in twix.quality_control
@test MagneticResonanceSignals.MeasHeaderEmpty in twix.quality_control

truncated_twix = copy(valid_twix_bytes)
truncated_twix = truncated_twix[1:900_000]
twix = @test_logs (Logging.Warn,r"Twix acquisition truncated at position 900000") #=
=# load_twix(IOBuffer(truncated_twix))
@test TriMRS.AcquisitionsIncomplete in twix.quality_control
@test MagneticResonanceSignals.AcquisitionsIncomplete in twix.quality_control
end

@testset "metadata parsing" begin
Expand Down Expand Up @@ -147,9 +147,9 @@ end


@testset "coil select parsing" begin
yaps_dict = TriMRS.parse_header_yaps(String(read("twix/PRESS_TE135_Breast_Coil_Headers/MeasYaps")))
yaps_dict = MagneticResonanceSignals.parse_header_yaps(String(read("twix/PRESS_TE135_Breast_Coil_Headers/MeasYaps")))
@test length(@test_logs((:warn, "Could not find some metadata for coil 7"),
TriMRS.parse_yaps_rx_coil_selection(yaps_dict))) == 7
MagneticResonanceSignals.parse_yaps_rx_coil_selection(yaps_dict))) == 7
end


12 changes: 6 additions & 6 deletions test/processing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@

# Know decimated z is numerically equal to Fourier downsampled z, as it's
# got no frequency content outside the window.
@test z[1:2:end] TriMRS.downsample_and_truncate(t, z, 0, 0, 2)[2] rtol=0 atol=1e-14
@test z[1:2:end] MagneticResonanceSignals.downsample_and_truncate(t, z, 0, 0, 2)[2] rtol=0 atol=1e-14

# Currently can't downsample to an odd number of samples
@test_throws InexactError TriMRS.downsample_and_truncate(t, z, 1, 0, 2)
@test_throws InexactError TriMRS.downsample_and_truncate(t, z, 0, 1, 2)
@test_throws InexactError TriMRS.downsample_and_truncate(t, z, 0, 0, 3)
@test_throws InexactError MagneticResonanceSignals.downsample_and_truncate(t, z, 1, 0, 2)
@test_throws InexactError MagneticResonanceSignals.downsample_and_truncate(t, z, 0, 1, 2)
@test_throws InexactError MagneticResonanceSignals.downsample_and_truncate(t, z, 0, 0, 3)

# Test timing.
@test t[3:2:end-2] == TriMRS.downsample_and_truncate(t, z, 2, 2, 2)[1]
@test t[2:end-1] == TriMRS.downsample_and_truncate(t, z, 1, 1, 1)[1]
@test t[3:2:end-2] == MagneticResonanceSignals.downsample_and_truncate(t, z, 2, 2, 2)[1]
@test t[2:end-1] == MagneticResonanceSignals.downsample_and_truncate(t, z, 1, 1, 1)[1]
end

@testset "coil_combination" begin
Expand Down
4 changes: 2 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using TriMRS
using MagneticResonanceSignals
using AxisArrays
using Test
using Unitful

# UUGH. See the improvements at https://github.com/JuliaArrays/AxisArrays.jl/pull/152
getaxis(samples, name) = AxisArrays.axes(samples, Axis{name}).val

@testset "TriMRS" begin
@testset "MagneticResonanceSignals" begin

include("core.jl")
include("mr_load.jl")
Expand Down

0 comments on commit b074286

Please sign in to comment.