server.R 25.6 KB
Newer Older
dmattek's avatar
dmattek committed
1
#
dmattek's avatar
dmattek committed
2
3
4
5
# Time Course Inspector: Shiny app for plotting time series data
# Author: Maciej Dobrzynski
#
# This is the server logic for a Shiny web application.
dmattek's avatar
dmattek committed
6
7
8
9
10
11
#

library(shiny)
library(shinyjs) #http://deanattali.com/shinyjs/
library(data.table)
library(ggplot2)
dmattek's avatar
dmattek committed
12
library(gplots) # for heatmap.2
dmattek's avatar
dmattek committed
13
library(plotly)
dmattek's avatar
dmattek committed
14
15
library(d3heatmap) # for interactive heatmap
library(dendextend) # for color_branches
16
library(colorspace) # for palettes (used to colour dendrogram)
dmattek's avatar
dmattek committed
17
library(RColorBrewer)
18
# sparcl temporarily unavailable on CRAN
dmattek's avatar
dmattek committed
19
library(sparcl) # sparse hierarchical and k-means
dmattek's avatar
dmattek committed
20
library(scales) # for percentages on y scale
dmattek's avatar
Added:    
dmattek committed
21
22
library(dtw) # for dynamic time warping
library(imputeTS) # for interpolating NAs
23
library(tca) # for time series manipulatiom, e.g. normTraj, genTraj, plotTrajRibbon
dmattek's avatar
dmattek committed
24

dmattek's avatar
dmattek committed
25
# change to increase the limit of the upload file size
dmattek's avatar
Added:    
dmattek committed
26
options(shiny.maxRequestSize = 200 * 1024 ^ 2)
dmattek's avatar
dmattek committed
27

dmattek's avatar
dmattek committed
28
# Server logic ----
dmattek's avatar
dmattek committed
29
shinyServer(function(input, output, session) {
30
  useShinyjs()
dmattek's avatar
dmattek committed
31
  
32
  # This is only set at session start
dmattek's avatar
dmattek committed
33
  # We use this as a way to determine which input was
34
35
  # clicked in the dataInBoth reactive
  counter <- reactiveValues(
dmattek's avatar
dmattek committed
36
37
38
    # The value of actionButton is the number of times the button is pressed
    dataGen1        = isolate(input$inDataGen1),
    dataLoadNuc     = isolate(input$inButLoadNuc),
39
40
    dataLoadTrajRem = isolate(input$inButLoadTrajRem),
    dataLoadStim    = isolate(input$inButLoadStim)
dmattek's avatar
dmattek committed
41
42
  )
  
dmattek's avatar
dmattek committed
43
  # UI-side-panel-data-load ----
dmattek's avatar
dmattek committed
44
  
dmattek's avatar
dmattek committed
45
  # Generate random dataset
46
47
48
  dataGen1 <- eventReactive(input$inDataGen1, {
    cat("dataGen1\n")
    
49
    return(tca::genTraj(in.nwells = 3))
50
51
  })
  
dmattek's avatar
dmattek committed
52
  # Load main data file
53
54
55
56
57
58
59
60
61
62
63
64
65
  dataLoadNuc <- eventReactive(input$inButLoadNuc, {
    cat("dataLoadNuc\n")
    locFilePath = input$inFileLoadNuc$datapath
    
    counter$dataLoadNuc <- input$inButLoadNuc - 1
    
    if (is.null(locFilePath) || locFilePath == '')
      return(NULL)
    else {
      return(fread(locFilePath))
    }
  })
  
dmattek's avatar
dmattek committed
66
67
68
69
  # This button will reset the inFileLoad
  observeEvent(input$butReset, {
    reset("inFileLoadNuc")  # reset is a shinyjs function
  })
70

dmattek's avatar
dmattek committed
71
  # Load data with trajectories to remove
72
73
74
75
76
77
78
79
80
81
82
83
  dataLoadTrajRem <- eventReactive(input$inButLoadTrajRem, {
    cat(file = stderr(), "dataLoadTrajRem\n")
    locFilePath = input$inFileLoadTrajRem$datapath
    
    counter$dataLoadTrajRem <- input$inButLoadTrajRem - 1
    
    if (is.null(locFilePath) || locFilePath == '')
      return(NULL)
    else {
      return(fread(locFilePath))
    }
  })
dmattek's avatar
dmattek committed
84
  
dmattek's avatar
dmattek committed
85
  # Load data with stimulation pattern
86
87
88
89
90
91
92
93
94
95
96
97
98
99
  dataLoadStim <- eventReactive(input$inButLoadStim, {
    cat(file = stderr(), "dataLoadStim\n")
    locFilePath = input$inFileLoadStim$datapath
    
    counter$dataLoadStim <- input$inButLoadStim - 1
    
    if (is.null(locFilePath) || locFilePath == '')
      return(NULL)
    else {
      return(fread(locFilePath))
    }
  })
  
    
dmattek's avatar
Added:    
dmattek committed
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
  # UI for loading csv with cell IDs for trajectory removal
  output$uiFileLoadTrajRem = renderUI({
    cat(file = stderr(), 'UI uiFileLoadTrajRem\n')
    
    if(input$chBtrajRem) 
      fileInput(
        'inFileLoadTrajRem',
        'Select data file (e.g. badTraj.csv) and press "Load Data"',
        accept = c('text/csv', 'text/comma-separated-values,text/plain')
      )
  })
  
  output$uiButLoadTrajRem = renderUI({
    cat(file = stderr(), 'UI uiButLoadTrajRem\n')
    
    if(input$chBtrajRem)
      actionButton("inButLoadTrajRem", "Load Data")
  })

119
120
121
  # UI for loading csv with stimulation pattern
  output$uiFileLoadStim = renderUI({
    cat(file = stderr(), 'UI uiFileLoadStim\n')
dmattek's avatar
Added:    
dmattek committed
122
    
123
124
125
126
127
128
129
130
131
132
    if(input$chBstim) 
      fileInput(
        'inFileLoadStim',
        'Select data file (e.g. stim.csv) and press "Load Data"',
        accept = c('text/csv', 'text/comma-separated-values,text/plain')
      )
  })
  
  output$uiButLoadStim = renderUI({
    cat(file = stderr(), 'UI uiButLoadStim\n')
dmattek's avatar
Added:    
dmattek committed
133
    
134
135
    if(input$chBstim)
      actionButton("inButLoadStim", "Load Data")
dmattek's avatar
Added:    
dmattek committed
136
137
  })
  
138

dmattek's avatar
dmattek committed
139
  
dmattek's avatar
dmattek committed
140
  # UI-side-panel-column-selection ----
dmattek's avatar
dmattek committed
141
142
143
  output$varSelTrackLabel = renderUI({
    cat(file = stderr(), 'UI varSelTrackLabel\n')
    locCols = getDataNucCols()
144
    locColSel = locCols[grep('(T|t)rack|ID|id', locCols)[1]] # index 1 at the end in case more matches; select 1st; matches TrackLabel, tracklabel, Track Label etc
dmattek's avatar
dmattek committed
145
146
147
    
    selectInput(
      'inSelTrackLabel',
dmattek's avatar
dmattek committed
148
      'Select Track Label (e.g. objNuc_TrackObjects_Label):',
dmattek's avatar
dmattek committed
149
150
151
152
153
154
155
156
157
      locCols,
      width = '100%',
      selected = locColSel
    )
  })
  
  output$varSelTime = renderUI({
    cat(file = stderr(), 'UI varSelTime\n')
    locCols = getDataNucCols()
158
    locColSel = locCols[grep('(T|t)ime|Metadata_T', locCols)[1]] # index 1 at the end in case more matches; select 1st; matches RealTime, realtime, real time, etc.
dmattek's avatar
dmattek committed
159
160
161
    
    selectInput(
      'inSelTime',
dmattek's avatar
dmattek committed
162
      'Select time column (e.g. Metadata_T, RealTime):',
dmattek's avatar
dmattek committed
163
164
165
166
167
      locCols,
      width = '100%',
      selected = locColSel
    )
  })
168
169
170
171

  output$varSelTimeFreq = renderUI({
    cat(file = stderr(), 'UI varSelTimeFreq\n')
    
172
173
174
175
176
177
178
179
180
181
    if (input$chBtrajInter) {
      numericInput(
        'inSelTimeFreq',
        'Provide time frequency:',
        min = 1,
        step = 1,
        width = '100%',
        value = 1
      )
    }
182
  })
dmattek's avatar
dmattek committed
183
  
dmattek's avatar
dmattek committed
184
  # This is the main field to select plot facet grouping
dmattek's avatar
dmattek committed
185
  # It's typically a column with the entire experimental description,
dmattek's avatar
dmattek committed
186
187
  # e.g.1 Stim_All_Ch or Stim_All_S.
  # e.g.2 a combination of 3 columns called Stimulation_...
dmattek's avatar
dmattek committed
188
189
190
  output$varSelGroup = renderUI({
    cat(file = stderr(), 'UI varSelGroup\n')
    
dmattek's avatar
dmattek committed
191
192
193
194
195
    if (input$chBgroup) {
      
      locCols = getDataNucCols()
      
      if (!is.null(locCols)) {
196
197
198
        locColSel = locCols[grep('(G|g)roup|(S|s)tim_All|(S|s)timulation|(S|s)ite', locCols)[1]]

        #cat('UI varSelGroup::locColSel ', locColSel, '\n')
dmattek's avatar
dmattek committed
199
200
201
202
203
204
205
206
        selectInput(
          'inSelGroup',
          'Select one or more facet groupings (e.g. Site, Well, Channel):',
          locCols,
          width = '100%',
          selected = locColSel,
          multiple = TRUE
        )
dmattek's avatar
dmattek committed
207
208
209
210
211
212
213
      }
    }
  })
  
  output$varSelSite = renderUI({
    cat(file = stderr(), 'UI varSelSite\n')
    
214
    if (input$chBtrackUni) {
dmattek's avatar
Added:    
dmattek committed
215
      locCols = getDataNucCols()
216
      locColSel = locCols[grep('(S|s)ite|(S|s)eries', locCols)[1]] # index 1 at the end in case more matches; select 1st
dmattek's avatar
Added:    
dmattek committed
217
218
219
220
221
222
223
224
225
      
      selectInput(
        'inSelSite',
        'Select FOV (e.g. Metadata_Site or Metadata_Series):',
        locCols,
        width = '100%',
        selected = locColSel
      )
    }
dmattek's avatar
dmattek committed
226
227
228
229
230
231
232
233
  })
  
  
  output$varSelMeas1 = renderUI({
    cat(file = stderr(), 'UI varSelMeas1\n')
    locCols = getDataNucCols()
    
    if (!is.null(locCols)) {
234
      locColSel = locCols[grep('objCyto_Intensity_MeanIntensity_imErkCor|(R|r)atio|(I|i)ntensity|y', locCols)[1]] # index 1 at the end in case more matches; select 1st
dmattek's avatar
dmattek committed
235

dmattek's avatar
dmattek committed
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
      selectInput(
        'inSelMeas1',
        'Select 1st measurement:',
        locCols,
        width = '100%',
        selected = locColSel
      )
    }
  })
  
  
  output$varSelMeas2 = renderUI({
    cat(file = stderr(), 'UI varSelMeas2\n')
    locCols = getDataNucCols()
    
    if (!is.null(locCols) &&
        !(input$inSelMath %in% c('', '1 / '))) {
253
      locColSel = locCols[grep('objNuc_Intensity_MeanIntensity_imErkCor', locCols)[1]] # index 1 at the end in case more matches; select 1st
dmattek's avatar
dmattek committed
254

dmattek's avatar
dmattek committed
255
256
257
258
259
260
261
262
263
264
      selectInput(
        'inSelMeas2',
        'Select 2nd measurement',
        locCols,
        width = '100%',
        selected = locColSel
      )
    }
  })
  
dmattek's avatar
dmattek committed
265
  # UI-side-panel-trim x-axis (time) ----
dmattek's avatar
dmattek committed
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
  output$uiSlTimeTrim = renderUI({
    cat(file = stderr(), 'UI uiSlTimeTrim\n')
    
    if (input$chBtimeTrim) {
      locTpts  = getDataTpts()
      
      if(is.null(locTpts))
        return(NULL)
      
      locRTmin = min(locTpts)
      locRTmax = max(locTpts)
      
      sliderInput(
        'slTimeTrim',
        label = 'Time range to include',
        min = locRTmin,
        max = locRTmax,
        value = c(locRTmin, locRTmax),
        step = 1
      )
      
    }
  })
dmattek's avatar
dmattek committed
289
  
dmattek's avatar
dmattek committed
290
  # UI-side-panel-normalization ----
dmattek's avatar
dmattek committed
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
  output$uiChBnorm = renderUI({
    cat(file = stderr(), 'UI uiChBnorm\n')
    
    if (input$chBnorm) {
      radioButtons(
        'rBnormMeth',
        label = 'Select method',
        choices = list('fold-change' = 'mean', 'z-score' = 'z.score')
      )
    }
  })
  
  output$uiSlNorm = renderUI({
    cat(file = stderr(), 'UI uiSlNorm\n')
    
    if (input$chBnorm) {
      locTpts  = getDataTpts()
      
      if(is.null(locTpts))
        return(NULL)
      
      locRTmin = min(locTpts)
      locRTmax = max(locTpts)
      
      sliderInput(
        'slNormRtMinMax',
        label = 'Time range for norm.',
        min = locRTmin,
        max = locRTmax,
dmattek's avatar
dmattek committed
320
321
        value = c(locRTmin, 0.1 * locRTmax), 
        step = 1
dmattek's avatar
dmattek committed
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
      )
    }
  })
  
  output$uiChBnormRobust = renderUI({
    cat(file = stderr(), 'UI uiChBnormRobust\n')
    
    if (input$chBnorm) {
      checkboxInput('chBnormRobust',
                    label = 'Robust stats',
                    FALSE)
    }
  })
  
  output$uiChBnormGroup = renderUI({
    cat(file = stderr(), 'UI uiChBnormGroup\n')
    
    if (input$chBnorm) {
      radioButtons('chBnormGroup',
dmattek's avatar
Mod:    
dmattek committed
341
                   label = 'Normalisation grouping',
342
                   choices = list('Entire dataset' = 'none', 'Per facet' = 'group', 'Per trajectory' = 'id'))
dmattek's avatar
dmattek committed
343
344
345
346
    }
  })
  
  
dmattek's avatar
dmattek committed
347
  # UI-side-panel-remove-outliers ----
dmattek's avatar
dmattek committed
348
349
350
351
  output$uiSlOutliers = renderUI({
    cat(file = stderr(), 'UI uiSlOutliers\n')
    
    if (input$chBoutliers) {
dmattek's avatar
Mod:    
dmattek committed
352
      
dmattek's avatar
dmattek committed
353
354
355
356
357
      sliderInput(
        'slOutliersPerc',
        label = 'Percentage of middle data',
        min = 90,
        max = 100,
dmattek's avatar
Fixed:    
dmattek committed
358
        value = 99.5, 
dmattek's avatar
dmattek committed
359
360
        step = 0.1
      )
dmattek's avatar
dmattek committed
361
      
dmattek's avatar
Mod:    
dmattek committed
362
      
dmattek's avatar
dmattek committed
363
364
365
    }
  })
  
dmattek's avatar
dmattek committed
366
  
dmattek's avatar
dmattek committed
367
  # Processing-data ----
dmattek's avatar
dmattek committed
368
  
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
  dataInBoth <- reactive({
    # Without direct references to inDataGen1,2 and inFileLoad, inDataGen2
    #    does not trigger running this reactive once inDataGen1 is used.
    # This is one of the more nuanced areas of reactive programming in shiny
    #    due to the if else logic, it isn't fetched once inDataGen1 is available
    # The morale is use direct retrieval of inputs to guarantee they are available
    #    for if else logic checks!
    
    locInGen1 = input$inDataGen1
    locInLoadNuc = input$inButLoadNuc
    #locInLoadStim = input$inButLoadStim
    
    cat(
      "dataInBoth\ninGen1: ",
      locInGen1,
      "   prev=",
      isolate(counter$dataGen1),
      "\ninDataNuc: ",
      locInLoadNuc,
      "   prev=",
      isolate(counter$dataLoadNuc),
      # "\ninDataStim: ",
      # locInLoadStim,
      # "   prev=",
      # isolate(counter$dataLoadStim),
      "\n"
    )
    
    # isolate the checks of counter reactiveValues
    # as we set the values in this same reactive
    if (locInGen1 != isolate(counter$dataGen1)) {
      cat("dataInBoth if inDataGen1\n")
      dm = dataGen1()
      # no need to isolate updating the counter reactive values!
      counter$dataGen1 <- locInGen1
    } else if (locInLoadNuc != isolate(counter$dataLoadNuc)) {
      cat("dataInBoth if inDataLoadNuc\n")
      dm = dataLoadNuc()
      # no need to isolate updating the counter reactive values!
      counter$dataLoadNuc <- locInLoadNuc
    } else {
      cat("dataInBoth else\n")
      dm = NULL
    }
    return(dm)
  })
  
  # return column names of the main dt
dmattek's avatar
dmattek committed
417
  getDataNucCols <- reactive({
418
419
420
421
422
423
424
425
426
427
428
    cat(file = stderr(), 'getDataNucCols: in\n')
    loc.dt = dataInBoth()
    
    if (is.null(loc.dt))
      return(NULL)
    else
      return(colnames(loc.dt))
  })
  
  # return dt with an added column with unique track object label
  dataMod <- reactive({
dmattek's avatar
dmattek committed
429
    cat(file = stderr(), 'dataMod\n')
430
431
    loc.dt = dataInBoth()
    
dmattek's avatar
dmattek committed
432
    if (is.null(loc.dt))
433
434
      return(NULL)
    
435
    if (input$chBtrackUni) {
dmattek's avatar
Added:    
dmattek committed
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
      loc.types = lapply(loc.dt, class)
      if(loc.types[[input$inSelTrackLabel]] %in% c('numeric', 'integer') & loc.types[[input$inSelSite]] %in% c('numeric', 'integer'))
      {
        loc.dt[, trackObjectsLabelUni := paste(sprintf("%03d", get(input$inSelSite)),
                                               sprintf("%04d", get(input$inSelTrackLabel)),
                                               sep = "_")]
      } else if(loc.types[[input$inSelTrackLabel]] %in% c('numeric', 'integer')) {
        loc.dt[, trackObjectsLabelUni := paste(sprintf("%s", get(input$inSelSite)),
                                               sprintf("%04d", get(input$inSelTrackLabel)),
                                               sep = "_")]
      } else if(loc.types[[input$inSelSite]] %in% c('numeric', 'integer')) {
        loc.dt[, trackObjectsLabelUni := paste(sprintf("%03d", get(input$inSelSite)),
                                               sprintf("%s", get(input$inSelTrackLabel)),
                                               sep = "_")]
      } else {
        loc.dt[, trackObjectsLabelUni := paste(sprintf("%s", get(input$inSelSite)),
                                               sprintf("%s", get(input$inSelTrackLabel)),
                                               sep = "_")]
      }
dmattek's avatar
Added:    
dmattek committed
455
    } else {
dmattek's avatar
Added:    
dmattek committed
456
      loc.dt[, trackObjectsLabelUni := get(input$inSelTrackLabel)]
dmattek's avatar
Added:    
dmattek committed
457
458
    }
    
dmattek's avatar
dmattek committed
459
    
dmattek's avatar
Added:    
dmattek committed
460
461
462
463
464
465
    # remove trajectories based on uploaded csv

    if (input$chBtrajRem) {
      cat(file = stderr(), 'dataMod: trajRem not NULL\n')
      
      loc.dt.rem = dataLoadTrajRem()
dmattek's avatar
dmattek committed
466
      loc.dt = loc.dt[!(trackObjectsLabelUni %in% loc.dt.rem[[1]])]
dmattek's avatar
Added:    
dmattek committed
467
468
    }
    
469
470
471
    return(loc.dt)
  })
  
dmattek's avatar
dmattek committed
472
473
474
475
476
  # return all unique track object labels (created in dataMod)
  # This will be used to display in UI for trajectory highlighting
  getDataTrackObjLabUni <- reactive({
    cat(file = stderr(), 'getDataTrackObjLabUni\n')
    loc.dt = dataMod()
477
    
dmattek's avatar
dmattek committed
478
479
480
481
    if (is.null(loc.dt))
      return(NULL)
    else
      return(unique(loc.dt$trackObjectsLabelUni))
482
483
  })
  
dmattek's avatar
Mod:    
dmattek committed
484
  
dmattek's avatar
dmattek committed
485
486
487
  # return all unique time points (real time)
  # This will be used to display in UI for box-plot
  # These timepoints are from the original dt and aren't affected by trimming of x-axis
dmattek's avatar
dmattek committed
488
489
490
  getDataTpts <- reactive({
    cat(file = stderr(), 'getDataTpts\n')
    loc.dt = dataMod()
491
    
dmattek's avatar
dmattek committed
492
493
494
495
    if (is.null(loc.dt))
      return(NULL)
    else
      return(unique(loc.dt[[input$inSelTime]]))
496
497
  })
  
dmattek's avatar
dmattek committed
498
  
499
500
501
  
  # prepare data for plotting time courses
  # returns dt with these columns:
dmattek's avatar
dmattek committed
502
  #    realtime - selected from input
dmattek's avatar
dmattek committed
503
  #    y        - measurement selected from input
dmattek's avatar
dmattek committed
504
  #               (can be a single column or result of an operation on two cols)
505
506
  #    id       - trackObjectsLabelUni; created in dataMod based on TrackObjects_Label
  #               and FOV column such as Series or Site (if TrackObjects_Label not unique across entire dataset)
dmattek's avatar
dmattek committed
507
508
  #    group    - grouping variable for facetting from input
  #    mid.in   - column with trajectory selection status from the input file or
509
510
511
512
  #               highlight status from UI 
  #               (column created if mid.in present in uploaded data or tracks are selected in the UI)
  #    obj.num  - created if ObjectNumber column present in the input data 
  #    pos.x,y  - created if columns with x and y positions present in the input data
513
  data4trajPlot <- reactive({
dmattek's avatar
dmattek committed
514
    cat(file = stderr(), 'data4trajPlot\n')
515
516
    
    loc.dt = dataMod()
dmattek's avatar
dmattek committed
517
    if (is.null(loc.dt))
518
519
      return(NULL)
    
520
    # create expression for 'y' column based on measurements and math operations selected in UI
dmattek's avatar
dmattek committed
521
    if (input$inSelMath == '')
522
523
524
525
526
527
      loc.s.y = input$inSelMeas1
    else if (input$inSelMath == '1 / ')
      loc.s.y = paste0(input$inSelMath, input$inSelMeas1)
    else
      loc.s.y = paste0(input$inSelMeas1, input$inSelMath, input$inSelMeas2)
    
528
    # create expression for 'group' column
529
530
    # creates a merged column based on other columns from input
    # used for grouping of plot facets
dmattek's avatar
dmattek committed
531
532
533
534
535
536
537
538
539
540
541
    if (input$chBgroup) {
      if(length(input$inSelGroup) == 0)
        return(NULL)
      
      loc.s.gr = sprintf("paste(%s, sep=';')",
                         paste(input$inSelGroup, sep = '', collapse = ','))
    } else {
      # if no grouping required, fill 'group' column with 0
      # because all the plotting relies on the presence of the group column
      loc.s.gr = "paste('0')"
    }
542
    
dmattek's avatar
dmattek committed
543
544

    # column name with time
545
546
    loc.s.rt = input$inSelTime
    
dmattek's avatar
dmattek committed
547
548
    # Assign tracks selected for highlighting in UI
    loc.tracks.highlight = input$inSelHighlight
549
    locButHighlight = input$chBhighlightTraj
dmattek's avatar
dmattek committed
550
    
dmattek's avatar
Added:    
dmattek committed
551
552
    
    # Find column names with position
553
    loc.s.pos.x = names(loc.dt)[grep('(L|l)ocation.*X|(P|p)os.x|(P|p)osx', names(loc.dt))[1]]
554
    loc.s.pos.y = names(loc.dt)[grep('(L|l)ocation.*Y|(P|p)os.y|(P|p)osy', names(loc.dt))[1]]
dmattek's avatar
Added:    
dmattek committed
555
    
556
    cat('Position columns: ', loc.s.pos.x, loc.s.pos.y, '\n')
557
558
    
    if (!is.na(loc.s.pos.x) & !is.na(loc.s.pos.y))
dmattek's avatar
Added:    
dmattek committed
559
560
561
562
      locPos = TRUE
    else
      locPos = FALSE
    
563
564
565
566
    
    # Find column names with ObjectNumber
    # This is different from TrackObject_Label and is handy to keep
    # because labels on segmented images are typically ObjectNumber
567
568
569
570
571
572
    loc.s.objnum = names(loc.dt)[grep('(O|o)bject(N|n)umber', names(loc.dt))[1]]
    #cat('data4trajPlot::loc.s.objnum ', loc.s.objnum, '\n')
    if (is.na(loc.s.objnum)) {
      locObjNum = FALSE
    }
    else {
dmattek's avatar
dmattek committed
573
      loc.s.objnum = loc.s.objnum[1]
574
      locObjNum = TRUE
dmattek's avatar
dmattek committed
575
    }
576
577
    
    
578
579
    # if dataset contains column mid.in with trajectory filtering status,
    # then, include it in plotting
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
    if (sum(names(loc.dt) %in% 'mid.in') > 0)
      locMidIn = TRUE
    else
      locMidIn = FALSE
    
    ## Build expression for selecting columns from loc.dt
    # Core columns
    s.colexpr = paste0('.(y = ', loc.s.y,
                       ', id = trackObjectsLabelUni', 
                       ', group = ', loc.s.gr,
                       ', realtime = ', loc.s.rt)
    
    # account for the presence of 'mid.in' column in uploaded data
    if(locMidIn)
      s.colexpr = paste0(s.colexpr, 
                         ', mid.in = mid.in')
    
    # include position x,y columns in uploaded data
    if(locPos)
      s.colexpr = paste0(s.colexpr, 
                         ', pos.x = ', loc.s.pos.x,
                         ', pos.y = ', loc.s.pos.y)
    
    # include ObjectNumber column
    if(locObjNum)
      s.colexpr = paste0(s.colexpr, 
                         ', obj.num = ', loc.s.objnum)
    
    # close bracket, finish the expression
    s.colexpr = paste0(s.colexpr, ')')
    
    # create final dt for output based on columns selected above
    loc.out = loc.dt[, eval(parse(text = s.colexpr))]
    
    
    # if track selection ON
    if (locButHighlight){
      # add a 3rd level with status of track selection
      # to a column with trajectory filtering status in the uploaded file
      if(locMidIn)
        loc.out[, mid.in := ifelse(id %in% loc.tracks.highlight, 'SELECTED', mid.in)]
      else
dmattek's avatar
Mod:    
dmattek committed
622
        # add a column with status of track selection
623
        loc.out[, mid.in := ifelse(id %in% loc.tracks.highlight, 'SELECTED', 'NOT SEL')]
624
    }
625
      
dmattek's avatar
dmattek committed
626

627
    ## Interpolate missing data and NA data points
628
    # From: https://stackoverflow.com/questions/28073752/r-how-to-add-rows-for-missing-values-for-unique-group-sequences
629
630
631
    # Tracks are interpolated only within first and last time points of every cell id
    # Datasets can have different realtime frequency (e.g. every 1', 2', etc),
    # or the frame number metadata can be missing, as is the case for tCourseSelected files that already have realtime column.
632
    # Therefore, we cannot rely on that info to get time frequency; user provides this number!
633
    
634
635
    setkey(loc.out, group, id, realtime)

636
637
    if (input$chBtrajInter) {
      # here we fill missing data with NA's
638
      loc.out = loc.out[setkey(loc.out[, .(seq(min(realtime, na.rm = T), max(realtime, na.rm = T), input$inSelTimeFreq)), by = .(group, id)], group, id, V1)]
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
      
      # x-check: print all rows with NA's
      print('Rows with NAs:')
      print(loc.out[rowSums(is.na(loc.out)) > 0, ])
      
      # NA's may be already present in the dataset'.
      # Interpolate (linear) them with na.interpolate as well
      if(locPos)
        s.cols = c('y', 'pos.x', 'pos.y')
      else
        s.cols = c('y')
      
      loc.out[, (s.cols) := lapply(.SD, na.interpolation), by = id, .SDcols = s.cols]
      
      
      # !!! Current issue with interpolation:
      # The column mid.in is not taken into account.
      # If a trajectory is selected in the UI,
      # the mid.in column is added (if it doesn't already exist in the dataset),
      # and for the interpolated point, it will still be NA. Not really an issue.
      #
      # Also, think about the current option of having mid.in column in the uploaded dataset.
      # Keep it? Expand it?
      # Create a UI filed for selecting the column with mid.in data.
      # What to do with that column during interpolation (see above)
      
    }    
dmattek's avatar
Mod:    
dmattek committed
666
    
667
    ## Trim x-axis (time)
dmattek's avatar
dmattek committed
668
669
670
    if(input$chBtimeTrim) {
      loc.out = loc.out[realtime >= input$slTimeTrim[[1]] & realtime <= input$slTimeTrim[[2]] ]
    }
dmattek's avatar
dmattek committed
671
    
672
    ## Normalization
673
    # F-n tca::normTraj adds additional column with .norm suffix
dmattek's avatar
dmattek committed
674
    if (input$chBnorm) {
675
      loc.out = tca::normTraj(
dmattek's avatar
dmattek committed
676
677
678
679
680
681
682
683
684
685
        in.dt = loc.out,
        in.meas.col = 'y',
        in.rt.col = 'realtime',
        in.rt.min = input$slNormRtMinMax[1],
        in.rt.max = input$slNormRtMinMax[2],
        in.type = input$rBnormMeth,
        in.robust = input$chBnormRobust,
        in.by.cols = if(input$chBnormGroup %in% 'none') NULL else input$chBnormGroup
      )
      
dmattek's avatar
dmattek committed
686
687
      # Column with normalized data is renamed to the original name
      # Further code assumes column name y produced by data4trajPlot
dmattek's avatar
dmattek committed
688
689
690
691
      loc.out[, y := NULL]
      setnames(loc.out, 'y.norm', 'y')
    }
    
dmattek's avatar
dmattek committed
692
693
694
695
696
697
    ##### MOD HERE
    ## display number of filtered tracks in textUI: uiTxtOutliers
    ## How? 
    ## 1. through reactive values?
    ## 2. through additional comumn to tag outliers?
    
dmattek's avatar
dmattek committed
698
699
700
701
702
703
704
705
706
707
    # Remove outliers
    # 1. Scale all points (independently per track)
    # 2. Pick time points that exceed the bounds
    # 3. Identify IDs of outliers
    # 4. Select cells that don't have these IDs
    
    cat('Ncells orig = ', length(unique(loc.out$id)), '\n')
    
    if (input$chBoutliers) {
      loc.out[, y.sc := scale(y)]  
708
709
      loc.tmp = loc.out[ y.sc < quantile(y.sc, (1 - input$slOutliersPerc * 0.01)*0.5, na.rm = T) | 
                           y.sc > quantile(y.sc, 1 - (1 - input$slOutliersPerc * 0.01)*0.5, na.rm = T)]
dmattek's avatar
dmattek committed
710
711
712
713
714
      loc.out = loc.out[!(id %in% unique(loc.tmp$id))]
      loc.out[, y.sc := NULL]
    }
    
    cat('Ncells trim = ', length(unique(loc.out$id)), '\n')
dmattek's avatar
Mod:    
dmattek committed
715
    
dmattek's avatar
dmattek committed
716
    return(loc.out)
dmattek's avatar
dmattek committed
717
718
  })
  
dmattek's avatar
dmattek committed
719
720
721
722
723
724
725
726
727
728
729
730
731
  
  
  # prepare data for clustering
  # return a matrix with:
  # cells as columns
  # time points as rows
  data4clust <- reactive({
    cat(file = stderr(), 'data4clust\n')
    
    loc.dt = data4trajPlot()
    if (is.null(loc.dt))
      return(NULL)
    
dmattek's avatar
Added:    
dmattek committed
732
    #print(loc.dt)
dmattek's avatar
dmattek committed
733
    loc.out = dcast(loc.dt, id ~ realtime, value.var = 'y')
dmattek's avatar
Added:    
dmattek committed
734
    #print(loc.out)
dmattek's avatar
dmattek committed
735
736
    loc.rownames = loc.out$id
    
dmattek's avatar
Mod:    
dmattek committed
737
    
dmattek's avatar
dmattek committed
738
739
    loc.out = as.matrix(loc.out[, -1])
    rownames(loc.out) = loc.rownames
dmattek's avatar
Added:    
dmattek committed
740
    
741
742
    # This might be removed entirely because all NA treatment happens in data4trajPlot
    # Clustering should work with NAs present. These might result from data itself or from missing time point rows that were turned into NAs when dcast-ing from long format.
dmattek's avatar
Added:    
dmattek committed
743
744
745
746
    # Remove NA's
    # na.interpolation from package imputeTS works with multidimensional data
    # but imputation is performed for each column independently
    # The matrix for clustering contains time series in rows, hence transposing it twice
747
    # loc.out = t(na.interpolation(t(loc.out)))
dmattek's avatar
Added:    
dmattek committed
748
    
dmattek's avatar
dmattek committed
749
    return(loc.out)
dmattek's avatar
Mod:    
dmattek committed
750
  }) 
dmattek's avatar
dmattek committed
751
  
dmattek's avatar
dmattek committed
752
  
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
  # prepare data with stimulation pattern
  # this dataset is displayed underneath of trajectory plot (modules/trajPlot.R) as geom_segment
  data4stimPlot <- reactive({
    cat(file = stderr(), 'data4stimPlot\n')
    
    if (input$chBstim) {
      cat(file = stderr(), 'data4stimPlot: stim not NULL\n')
      
      loc.dt.stim = dataLoadStim()
      return(loc.dt.stim)
    } else {
      cat(file = stderr(), 'data4stimPlot: stim is NULL\n')
      return(NULL)
    }
  })
  
dmattek's avatar
Added:    
dmattek committed
769
770
771
772
773
774
775
776
777
  # download data as prepared for plotting
  # after all modification
  output$downloadDataClean <- downloadHandler(
    filename = 'tCoursesSelected_clean.csv',
    content = function(file) {
      write.csv(data4trajPlot(), file, row.names = FALSE)
    }
  )
  
dmattek's avatar
dmattek committed
778
779
780
  # Plotting-trajectories ----

  # UI for selecting trajectories
781
  # The output data table of data4trajPlot is modified based on inSelHighlight field
dmattek's avatar
dmattek committed
782
783
  output$varSelHighlight = renderUI({
    cat(file = stderr(), 'UI varSelHighlight\n')
dmattek's avatar
dmattek committed
784
    
dmattek's avatar
dmattek committed
785
786
787
    locBut = input$chBhighlightTraj
    if (!locBut)
      return(NULL)
dmattek's avatar
dmattek committed
788
    
dmattek's avatar
dmattek committed
789
    loc.v = getDataTrackObjLabUni()
dmattek's avatar
dmattek committed
790
    if (!is.null(loc.v)) {
791
      selectInput(
dmattek's avatar
dmattek committed
792
793
794
        'inSelHighlight',
        'Select one or more rajectories:',
        loc.v,
795
        width = '100%',
dmattek's avatar
dmattek committed
796
        multiple = TRUE
797
      )
dmattek's avatar
dmattek committed
798
799
800
    }
  })
  
dmattek's avatar
dmattek committed
801
802
803
804
805
806
807
808
809
810
811
812
813
814
  # Trajectory plotting - ribbon
  callModule(modTrajRibbonPlot, 'modTrajRibbon', 
             in.data = data4trajPlot,
             in.data.stim = data4stimPlot,
             in.fname = function() return( "tCoursesMeans.pdf"))
  
  ###### Trajectory plotting - individual
  callModule(modTrajPlot, 'modTrajPlot', 
             in.data = data4trajPlot, 
             in.data.stim = data4stimPlot,
             in.fname = function() {return( "tCourses.pdf")})
  
  
  # Tabs ----
815
  ###### AUC calculation and plotting
816
  callModule(modAUCplot, 'tabAUC', data4trajPlot, in.fname = function() return('boxplotAUC.pdf'))
dmattek's avatar
Added:    
dmattek committed
817
  
dmattek's avatar
Added:    
dmattek committed
818
  ###### Box-plot
819
  callModule(tabBoxPlot, 'tabBoxPlot', data4trajPlot, in.fname = function() return('boxplotTP.pdf'))
dmattek's avatar
dmattek committed
820
  
dmattek's avatar
dmattek committed
821
  ###### Scatter plot
822
  callModule(tabScatterPlot, 'tabScatter', data4trajPlot, in.fname = function() return('scatter.pdf'))
dmattek's avatar
dmattek committed
823
  
dmattek's avatar
dmattek committed
824
  ##### Hierarchical clustering
825
  callModule(clustHier, 'tabClHier', data4clust, data4trajPlot, data4stimPlot)
dmattek's avatar
dmattek committed
826
827
  
  ##### Sparse hierarchical clustering using sparcl
828
  callModule(clustHierSpar, 'tabClHierSpar', data4clust, data4trajPlot, data4stimPlot)
dmattek's avatar
dmattek committed
829

dmattek's avatar
Mod:    
dmattek committed
830
  
dmattek's avatar
dmattek committed
831
})