/
main.go
259 lines (208 loc) · 6.43 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
package main
import (
"bufio"
"context"
"encoding/csv"
"flag"
"fmt"
"log"
"os"
"sort"
"strings"
"time"
"cloud.google.com/go/storage"
_ "github.com/carbocation/genomisc/compileinfoprint"
)
const (
SampleIDColumnName = "sample_id"
InstanceColumnName = "instance"
)
var (
TimepointColumnName = "trigger_time"
DicomColumnName = "dicom_file"
)
// Safe for concurrent use by multiple goroutines so we'll make this a global
var client *storage.Client
func main() {
defer func() { log.Println("Quitting") }()
var manifest, folder, suffix, sampleList string
var delay int
var grid, withTransparency bool
flag.StringVar(&manifest, "manifest", "", "Path to manifest file")
flag.StringVar(&folder, "folder", "", "Path to google storage folder that contains PNGs")
flag.StringVar(&suffix, "suffix", ".png", "Suffix after .dcm. Typically .png for raw dicoms or .png.overlay.png for merged dicoms.")
flag.StringVar(&sampleList, "samples", "", "To run in batch mode, provide a file that contains one sample (format: sampleid_instance) per line.")
flag.StringVar(&DicomColumnName, "dicom_column_name", "dicom_file", "Name of the column in the manifest with the dicom.")
flag.StringVar(&TimepointColumnName, "sequence_column_name", "trigger_time", "Name of the column that indicates the order of the images with an increasing number.")
flag.IntVar(&delay, "delay", 2, "Milliseconds between each frame of the gif.")
flag.BoolVar(&grid, "grid", true, "If multiple series are included, display as grid? (If false, will display sequentially)")
flag.BoolVar(&withTransparency, "transparency", false, "If true, the gif will reserve a color in its palette for transparency")
flag.Parse()
if manifest == "" || folder == "" {
flag.PrintDefaults()
os.Exit(1)
}
folder = strings.TrimSuffix(folder, "/")
// Initialize the Google Storage client, but only if our folder indicates
// that we are pointing to a Google Storage path.
if strings.HasPrefix(folder, "gs://") {
var err error
client, err = storage.NewClient(context.Background())
if err != nil {
log.Fatalln(err)
}
}
if sampleList != "" {
if err := runBatch(sampleList, manifest, folder, suffix, delay, grid, withTransparency); err != nil {
log.Fatalln(err)
}
return
}
if err := run(manifest, folder, suffix, delay, grid, withTransparency); err != nil {
log.Fatalln(err)
}
}
func runBatch(sampleList, manifest, folder, suffix string, delay int, grid bool, withTransparency bool) error {
fmt.Println("Batch mode animated Gif maker")
man, err := parseManifest(manifest)
if err != nil {
return err
}
f, err := os.Open(sampleList)
if err != nil {
return err
}
r := csv.NewReader(f)
samples, err := r.ReadAll()
if err != nil {
return err
}
fmt.Printf("Identified %d lines in the sample list\n", len(samples))
for _, sampleRow := range samples {
if len(sampleRow) != 1 {
fmt.Printf("Expected sample row input to have one entry. Instead saw %v. Skipping.", sampleRow)
continue
}
sample := sampleRow[0]
input := strings.Split(sample, "_")
if len(input) != 2 {
fmt.Printf("Expected sampleid separated from instance by an underscore (_). Instead saw %v. Skipping.", input)
continue
}
key := manifestKey{SampleID: input[0], Instance: input[1]}
entries, exists := man[key]
if !exists {
fmt.Println(key, "not found in the manifest")
continue
}
sort.Slice(entries, func(i, j int) bool { return entries[i].timepoint < entries[j].timepoint })
pngs := make([]string, 0, len(entries))
for _, entry := range entries {
pngs = append(pngs, folder+"/"+entry.dicom+suffix)
}
outName := key.SampleID + "_" + key.Instance + ".gif"
errchan := make(chan error)
fmt.Printf("Fetching images for %+v %s_%s", key, key.SampleID, key.Instance)
go func() {
if grid {
errchan <- makeOneGrid(pngs, outName, delay, withTransparency)
} else {
errchan <- makeOneGif(pngs, outName, delay, withTransparency)
}
}()
WaitLoop:
for {
select {
case err = <-errchan:
fmt.Printf("\n")
if err != nil {
fmt.Println("Error making gif:", err.Error())
}
break WaitLoop
}
}
if err == nil {
fmt.Println("Successfully created", outName, "for", fmt.Sprintf("%s_%s", key.SampleID, key.Instance))
}
}
return nil
}
func run(manifest, folder, suffix string, delay int, grid bool, withTransparency bool) error {
fmt.Println("Animated Gif maker")
man, err := parseManifest(manifest)
if err != nil {
return err
}
rdr := bufio.NewReader(os.Stdin)
fmt.Println("We are aware of", len(man), "samples in the manifest")
fmt.Println("An example of the sampleid_instance format is: 1234567_2")
fmt.Println("Enter 'rand' for a random entry")
fmt.Println("Enter 'q' to quit")
fmt.Println("---------------------")
tick := time.NewTicker(1 * time.Second)
for {
fmt.Print("[sampleid_instance]> ")
text, err := rdr.ReadString('\n')
if err != nil {
return err
}
text = strings.ReplaceAll(text, "\n", "")
if text == "q" {
fmt.Println("quitting")
break
}
var key manifestKey
if text == "rand" {
for k := range man {
key = k
break
}
} else {
input := strings.Split(text, "_")
if len(input) != 2 {
fmt.Println("Expected sampleid separated from instance by an underscore (_)")
continue
}
key = manifestKey{SampleID: input[0], Instance: input[1]}
}
entries, exists := man[key]
if !exists {
fmt.Println(key, "not found in the manifest")
continue
}
sort.Slice(entries, func(i, j int) bool { return entries[i].timepoint < entries[j].timepoint })
pngs := make([]string, 0, len(entries))
for _, entry := range entries {
pngs = append(pngs, folder+"/"+entry.dicom+suffix)
}
outName := key.SampleID + "_" + key.Instance + ".gif"
errchan := make(chan error)
fmt.Printf("Fetching images for %+v %s_%s", key, key.SampleID, key.Instance)
started := time.Now()
go func() {
if grid {
errchan <- makeOneGrid(pngs, outName, delay, withTransparency)
} else {
errchan <- makeOneGif(pngs, outName, delay, withTransparency)
}
}()
WaitLoop:
for {
select {
case err = <-errchan:
fmt.Printf("\n")
if err != nil {
fmt.Println("Error making gif:", err.Error())
}
break WaitLoop
case current := <-tick.C:
fmt.Printf("\rFetching images for %+v (%s)", key, current.Sub(started))
}
}
if err == nil {
fmt.Println("Successfully created", outName, "for", fmt.Sprintf("%s_%s", key.SampleID, key.Instance))
}
continue
}
return nil
}