Skip to content

Commit 2327777

Browse files
committed
Made mri-like example for Multik inside dataframe
1 parent 27fd209 commit 2327777

File tree

1 file changed

+95
-114
lines changed
  • examples/idea-examples/unsupported-data-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/examples/multik

1 file changed

+95
-114
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,115 @@
11
package org.jetbrains.kotlinx.dataframe.examples.multik
22

3+
import kotlinx.datetime.LocalDate
34
import org.jetbrains.kotlinx.dataframe.annotations.DataSchema
5+
import org.jetbrains.kotlinx.dataframe.api.append
6+
import org.jetbrains.kotlinx.dataframe.api.cast
7+
import org.jetbrains.kotlinx.dataframe.api.mapToFrame
48
import org.jetbrains.kotlinx.dataframe.api.print
9+
import org.jetbrains.kotlinx.dataframe.api.single
510
import org.jetbrains.kotlinx.dataframe.api.toDataFrame
6-
import org.jetbrains.kotlinx.dataframe.io.toStandaloneHtml
7-
import org.jetbrains.kotlinx.multik.api.identity
811
import org.jetbrains.kotlinx.multik.api.mk
9-
import org.jetbrains.kotlinx.multik.ndarray.data.D2Array
10-
import org.jetbrains.kotlinx.multik.ndarray.data.set
11-
import kotlin.math.cos
12-
import kotlin.math.sin
13-
import kotlin.math.tan
14-
15-
@DataSchema
16-
data class Transformation(
17-
val type: TransformationType,
18-
val parameters: Map<String, Double>,
19-
val note: String,
20-
val matrix: D2Array<Double>,
21-
)
22-
23-
enum class TransformationType {
24-
IDENTITY,
25-
TRANSLATION,
26-
SCALING,
27-
ROTATION,
28-
SHEARING,
29-
REFLECTION_ABOUT_ORIGIN,
30-
REFLECTION_ABOUT_X_AXIS,
31-
REFLECTION_ABOUT_Y_AXIS,
32-
}
12+
import org.jetbrains.kotlinx.multik.api.rand
13+
import org.jetbrains.kotlinx.multik.ndarray.data.D3Array
14+
import org.jetbrains.kotlinx.multik.ndarray.data.D4Array
15+
import java.time.Month.JULY
3316

3417
/**
35-
* IDK yet about this one... TODO
18+
* DataFrames can store anything inside, including Multik ndarrays.
19+
* This can be useful for storing matrices for easier access later or to simply organize data read from other files.
20+
* For example, MRI data is often stored as 3D arrays and sometimes even 4D arrays.
3621
*/
3722
fun main() {
38-
// DataFrames can store anything inside, including Multik nd arrays.
39-
// This can be useful for storing matrices for easier access later,
40-
// such as affine transformations when making 2D graphics!
41-
// (https://en.wikipedia.org/wiki/Affine_transformation)
42-
43-
// let's make a transformation sequence that rotates and scales an image in place.
44-
// It's currently 100x50, positioned with its left bottom corner at (x=10, y=0)
45-
val transformations = listOf(
46-
Transformation(
47-
type = TransformationType.TRANSLATION,
48-
parameters = mapOf("x" to -10.0, "y" to 0.0),
49-
note = "Translate so left-bottom touches origin",
50-
matrix = translationMatrixOf(x = -10.0, y = 0.0),
51-
),
52-
Transformation(
53-
type = TransformationType.SCALING,
54-
parameters = mapOf("w" to 2.0, "h" to 2.0),
55-
note = "Scale by x2",
56-
matrix = scaleMatrixOf(w = 2.0, h = 2.0),
57-
),
58-
Transformation(
59-
type = TransformationType.TRANSLATION,
60-
parameters = mapOf("x" to -100.0, "y" to -50.0),
61-
note = "Translate so the new image center is at the origin",
62-
matrix = translationMatrixOf(x = -100.0, y = -50.0),
63-
),
64-
Transformation(
65-
type = TransformationType.ROTATION,
66-
parameters = mapOf("angle" to 45.0),
67-
note = "Rotate by 45 degrees",
68-
matrix = rotationMatrixOf(angle = 45.0),
69-
),
70-
Transformation(
71-
type = TransformationType.TRANSLATION,
72-
parameters = mapOf("x" to 10.0 + 50.0, "y" to 0.0 + 25.0),
73-
note = "Translate back so the center is at the same original position",
74-
matrix = translationMatrixOf(x = 10.0 + 50.0, y = 0.0 + 25.0),
75-
),
23+
// imaginary list of patient data
24+
@Suppress("ktlint:standard:argument-list-wrapping")
25+
val metadata = listOf(
26+
MriMetadata(10012L, 25, "Healthy", LocalDate(2023, 1, 1)),
27+
MriMetadata(10013L, 45, "Tuberculosis", LocalDate(2023, 2, 15)),
28+
MriMetadata(10014L, 32, "Healthy", LocalDate(2023, 3, 22)),
29+
MriMetadata(10015L, 58, "Pneumonia", LocalDate(2023, 4, 8)),
30+
MriMetadata(10016L, 29, "Tuberculosis", LocalDate(2023, 5, 30)),
31+
MriMetadata(10017L, 42, "Healthy", LocalDate(2023, 6, 15)),
32+
MriMetadata(10018L, 37, "Healthy", LocalDate(2023, 7, 1)),
33+
MriMetadata(10019L, 55, "Healthy", LocalDate(2023, 8, 15)),
34+
MriMetadata(10020L, 28, "Healthy", LocalDate(2023, 9, 1)),
35+
MriMetadata(10021L, 44, "Healthy", LocalDate(2023, 10, 15)),
36+
MriMetadata(10022L, 31, "Healthy", LocalDate(2023, 11, 1)),
7637
).toDataFrame()
7738

78-
transformations.print(borders = true)
79-
transformations.toStandaloneHtml().openInBrowser()
80-
}
81-
82-
fun identityMatrix(): D2Array<Double> = mk.identity(3)
39+
// "reading" the results from "files"
40+
val results = metadata.mapToFrame {
41+
+patientId
42+
+age
43+
+diagnosis
44+
+scanDate
45+
"t1WeightedMri" from { readT1WeightedMri(patientId) }
46+
"fMriBoldSeries" from { readFMRiBoldSeries(patientId) }
47+
}.cast<MriResults>(verify = true)
48+
.append()
8349

84-
/** Returns a 3x3 affine transformation matrix that translates by (x, y) */
85-
fun translationMatrixOf(x: Double = 0.0, y: Double = 0.0): D2Array<Double> =
86-
identityMatrix().apply {
87-
this[0, 2] = x
88-
this[1, 2] = y
89-
}
50+
results.print(borders = true)
9051

91-
/** Returns a 3x3 affine transformation matrix that scales by (w, h) about the origin */
92-
fun scaleMatrixOf(w: Double = 1.0, h: Double = 1.0): D2Array<Double> =
93-
identityMatrix().apply {
94-
this[0, 0] = w
95-
this[1, 1] = h
96-
}
52+
// now when we want to check and visualize the T1-weighted MRI scan
53+
// for that one healthy patient in July, we can do:
54+
val scan = results
55+
.single { scanDate.month == JULY && diagnosis == "Healthy" }
56+
.t1WeightedMri
9757

98-
/** Returns a 3x3 affine transformation matrix that rotates by [angle] degrees about the origin */
99-
fun rotationMatrixOf(angle: Double): D2Array<Double> {
100-
val cos = cos(angle)
101-
val sin = sin(angle)
102-
return identityMatrix().apply {
103-
this[0, 0] = cos
104-
this[0, 1] = -sin
105-
this[1, 0] = sin
106-
this[1, 1] = cos
107-
}
58+
// easy :)
59+
visualize(scan)
10860
}
10961

110-
/** Returns a 3x3 affine transformation matrix that shears by [x] and [y] */
111-
fun shearingMatrixOf(x: Double = 0.0, y: Double = 0.0): D2Array<Double> =
112-
identityMatrix().apply {
113-
this[0, 1] = tan(x)
114-
this[1, 0] = tan(y)
115-
}
62+
@DataSchema
63+
data class MriMetadata(
64+
/** Unique patient ID. */
65+
val patientId: Long,
66+
/** Patient age. */
67+
val age: Int,
68+
/** Clinical diagnosis (e.g. "Healthy", "Tuberculosis") */
69+
val diagnosis: String,
70+
/** Date of the scan */
71+
val scanDate: LocalDate,
72+
)
73+
74+
@DataSchema
75+
data class MriResults(
76+
/** Unique patient ID. */
77+
val patientId: Long,
78+
/** Patient age. */
79+
val age: Int,
80+
/** Clinical diagnosis (e.g. "Healthy", "Tuberculosis") */
81+
val diagnosis: String,
82+
/** Date of the scan */
83+
val scanDate: LocalDate,
84+
/**
85+
* T1-weighted anatomical MRI scan.
86+
*
87+
* Dimensions: (256 x 256 x 180)
88+
* - 256 width x 256 height
89+
* - 180 slices
90+
*/
91+
val t1WeightedMri: D3Array<Float>,
92+
/**
93+
* Blood oxygenation level-dependent (BOLD) time series from an fMRI scan.
94+
*
95+
* Dimensions: (64 x 64 x 30 x 200)
96+
* - 64 width x 64 height
97+
* - 30 slices
98+
* - 200 timepoints
99+
*/
100+
val fMriBoldSeries: D4Array<Float>,
101+
)
116102

117-
/** Returns a 3x3 affine transformation matrix that reflects about the origin */
118-
fun reflectionAboutOriginMatrix(): D2Array<Double> =
119-
identityMatrix().apply {
120-
this[0, 0] = -1.0
121-
this[1, 1] = -1.0
122-
}
103+
fun readT1WeightedMri(id: Long): D3Array<Float> {
104+
// This should in practice, of course, read the actual data, but for this example we just return a dummy array
105+
return mk.rand(256, 256, 180)
106+
}
123107

124-
/** Returns a 3x3 affine transformation matrix that reflects about the x-axis */
125-
fun reflectionAboutXAxisMatrix(): D2Array<Double> =
126-
identityMatrix().apply {
127-
this[1, 1] = -1.0
128-
}
108+
fun readFMRiBoldSeries(id: Long): D4Array<Float> {
109+
// This should in practice, of course, read the actual data, but for this example we just return a dummy array
110+
return mk.rand(64, 64, 30, 200)
111+
}
129112

130-
/** Returns a 3x3 affine transformation matrix that reflects about the y-axis */
131-
fun reflectionAboutYAxisMatrix(): D2Array<Double> =
132-
identityMatrix().apply {
133-
this[0, 0] = -1.0
134-
}
113+
fun visualize(scan: D3Array<Float>) {
114+
// This would then actually visualize the scan
115+
}

0 commit comments

Comments
 (0)