Last active
February 1, 2021 21:40
-
-
Save agirault/c32ccdc6755698f720d876374bebfa92 to your computer and use it in GitHub Desktop.
Generate the anatomical labels for each side of the screen given a dataset (orientation) and its display mode (data vs world mapping)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import simd | |
enum Axis { | |
enum Data { | |
case I | |
case J | |
case K | |
} | |
enum World { | |
case X // LPS: X = R->L (sagittal) | |
case Y // LPS: Y = A->P (coronal) | |
case Z // LPS: Z = I->S (axial) | |
} | |
case data(Data) | |
case world(World) | |
} | |
enum Side { | |
case top | |
case bottom | |
case left | |
case right | |
case close | |
case far | |
var coordinates: simd_double3 { | |
// no transform & LPS (axial view) | |
switch self { | |
case .top: return simd_double3(0, -1, 0) | |
case .bottom: return simd_double3(0, 1, 0) | |
case .left: return simd_double3(-1, 0, 0) | |
case .right: return simd_double3(1, 0, 0) | |
case .close: return simd_double3(0, 0, -1) | |
case .far: return simd_double3(0, 0, 1) | |
} | |
} | |
} | |
enum Label: CaseIterable { | |
case left | |
case right | |
case posterior | |
case anterior | |
case superior | |
case inferior | |
var name: String { | |
switch self { | |
case .left: return "Left" | |
case .right: return "Right" | |
case .posterior: return "Posterior" | |
case .anterior: return "Anterior" | |
case .superior: return "Superior" | |
case .inferior: return "Inferior" | |
} | |
} | |
var initial: String { | |
return name.first!.uppercased() | |
} | |
var coordinates: simd_int3 { | |
// in LPS coordinate system | |
switch self { | |
case .left: return simd_int3(1, 0, 0) | |
case .right: return simd_int3(-1, 0, 0) | |
case .posterior: return simd_int3(0, 1, 0) | |
case .anterior: return simd_int3(0, -1, 0) | |
case .superior: return simd_int3(0, 0, 1) | |
case .inferior: return simd_int3(0, 0, -1) | |
} | |
} | |
init(location side: Side, axis: Axis, orientation R: simd_double3x3) { | |
switch axis { | |
case .data(let dataAxis): | |
// Adjust if not K (scan direction) | |
var M: simd_double3x3 | |
switch dataAxis { | |
case .I: | |
M = simd_double3x3(simd_double3(0, 1.0, 0), simd_double3(0, 0, -1.0), simd_double3(-1.0, 0, 0)) | |
case .J: | |
M = simd_double3x3(simd_double3(1.0, 0, 0), simd_double3(0, 0, -1.0), simd_double3(0, 1.0, 0)) | |
case .K: | |
M = matrix_identity_double3x3 | |
} | |
let MR = M * R | |
// Apply the orientation matrix to the side coordinates | |
let outV3 = MR * side.coordinates | |
// Find the closest axis | |
func rounded(val: Double) -> Int32 { | |
let threshold = sqrt(0.5) | |
if val < -threshold { | |
return -1 | |
} | |
if val > threshold { | |
return 1 | |
} | |
return 0 | |
} | |
let outV3Rounded = simd_int3( | |
rounded(val: outV3.x), | |
rounded(val: outV3.y), | |
rounded(val: outV3.z) | |
) | |
// Get the label for that vector | |
self = Label.allCases.first(where: { $0.coordinates == outV3Rounded })! | |
case .world(let worldAxis): | |
switch worldAxis { | |
case .X: | |
switch side { | |
case .top: self = .superior | |
case .bottom: self = .inferior | |
case .left: self = .anterior | |
case .right: self = .posterior | |
case .close: self = .left | |
case .far: self = .right | |
} | |
case .Y: | |
switch side { | |
case .top: self = .superior | |
case .bottom: self = .inferior | |
case .left: self = .right | |
case .right: self = .left | |
case .close: self = .anterior | |
case .far: self = .posterior | |
} | |
case .Z: | |
switch side { | |
case .top: self = .anterior | |
case .bottom: self = .posterior | |
case .left: self = .right | |
case .right: self = .left | |
case .close: self = .inferior | |
case .far: self = .superior | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment