Project

General

Profile

« Previous | Next » 

Revision 29eea01f

Added by Benoit Parmentier over 9 years ago

mosaicing test scaling up adding sine weights and cleaning up code with other options

View differences:

climate/research/oregon/interpolation/global_run_scalingup_mosaicing.R
5 5
#Analyses, figures, tables and data are also produced in the script.
6 6
#AUTHOR: Benoit Parmentier 
7 7
#CREATED ON: 04/14/2015  
8
#MODIFIED ON: 05/25/2015            
8
#MODIFIED ON: 05/27/2015            
9 9
#Version: 4
10 10
#PROJECT: Environmental Layers project     
11 11
#COMMENTS: analyses for run 10 global analyses,all regions 1500x4500km and other tiles
......
69 69
  return(out_dir)
70 70
}
71 71

  
72
sine_structure_fun <-function(x,T,phase,a,b,use_cos=FALSE){
73
  
74
  #Create sine for a one dimensional series
75
  #Note that sine function uses radian unit.
76
  #a=amplitude
77
  #b=mean or amplitude 0 of the series
78
  #T= stands for period definition
79
  #phase=phase angle (in radian!!)
80
  #cos: use cosine instead of sine if TRUE
81
  
82
  if(use_cos==FALSE){
83
    y <- a*sin((x*pi/T)+ phase) + b
84
  }else{
85
    y <- a*cos((x*pi/T)+ phase) + b
86
  }
87
  return(y)
88
}
89

  
72 90
create_weights_fun <- function(i, list_param){
73 91
  #This function generates weights from a point location on a raster layer.
74 92
  #Note that the weights are normatlized on 0-1 scale using max and min values.
75 93
  #Inputs:
76 94
  #lf: list of raster files
77
  #df_points: 
95
  #df_points: reference points from which to compute distance
96
  #r_feature: reference features as raster image from which to compute distance from
97
  #methods: options available: use_sine_weights,use_edge,use_linear_weights
78 98
  #Outputs:
79
  #TODO: add options to generate weights from edges/reference image rather than reference centroids points...
99
  #raster list of weights and product of wegihts and inuts
100
  #TODO: 
101
  # -use gdal proximity for large files and use_edge option
102
  # - add raster options
103
  # - improve efficiency
104
  # - change name options
105
  #
80 106
  ############
81 107
  
82 108
  lf <- list_param$lf
83
  df_centroids <- list_param$df_points
84
  feature_raster <- list_param$feature_raster
85
  use_edge <- list_param$use_edge
109
  df_points <- list_param$df_points
110
  r_feature <- list_param$r_feature #this should be change to a list
111
  padding <- TRUE #if padding true then make buffer around edges??
112
  method <- list_param$method #differnt methods available to create weights
113
  #NAflag,file_format,out_suffix etc...
86 114
  out_dir_str <- list_param$out_dir_str
87 115
    
88 116
  ####### START SCRIPT #####
89 117
  
90
  r1 <- raster(lf[i]) #input image
118
  r_in <- raster(lf[i]) #input image
91 119

  
92 120
  set1f <- function(x){rep(NA, x)}
93
  r_init <- init(r1, fun=set1f)
121
  r_init <- init(r_in, fun=set1f)
94 122

  
95
  if(!is.null(df_centroids)){
96
    cell_ID <- cellFromXY(r_init,xy=df_centroids[i,])
97
    r_init[cell_ID] <- df_centroids$ID[i]
123
  if(!is.null(r_feature)){
124
    r_init <- r_feature
98 125
  }
99 126

  
100
  if(!is.null(feature_raster)){
101
    cell_ID <- cellFromXY(r_init,xy=df_centroids[i,])
102
    r_init[cell_ID] <- df_centroids$ID[i]
127
  if(!is.null(df_points)){ #reference points as SPDF object
128
    cell_ID <- cellFromXY(r_init,xy=df_points[i,])
129
    r_init[cell_ID] <- df_points$ID[i]
103 130
  }
104 131

  
105
  #change here to distance from edges...
106

  
107
  #if(use_edge==T){
108
  #    n_col <- ncol(r_init)
109
  #    n_row <- nrow(r_init)
110
  #  
111
  #    #xfrom
112
  #    r_init[1,1:n_col] <- 1
113
  #    r_init[n_row,1:n_col] <- 1
114
  #    r_init[1:n_row,1] <- 1
115
  #    r_init[1:n_row,n_col] <- 1
132
  if(method=="use_sine_weights"){
133
    #Generate spatial pattern 5:     
134
    n_col <- ncol(r_init)
135
    n_row <- nrow(r_init)
136

  
137
    #u <- xFromCol(r_init,col=1:n_col)
138
    #add padding option later...buffer from a specific distance and tailling of at 0.1
139
    u <- 1:n_col
140
    a<- 1 #amplitude in this case
141
    b<- 0
142
    T<- n_col
143
    phase <- 0
144
    use_cos <- FALSE
145
    ux <- sine_structure_fun(u,T,phase,a,b,use_cos)
146
    ux_rep <-rep(ux,time=n_row)  
147
    r1 <-setValues(r_init,ux_rep)  #note efficient in memory might need to revise this
148
    #plot(r)
149
  
150
    v <- 1:n_row
151
    a<- 1 #amplitude in this case
152
    b<- 0
153
    T<- n_row
154
    phase <- 0
155
    use_cos <- FALSE
156
    vx <- sine_structure_fun(v,T,phase,a,b,use_cos)
157
    vx_rep <- unlist((lapply(1:n_row,FUN=function(i){rep(vx[i],time=n_col)})))  
158
  
159
    r2 <-setValues(r_init,vx_rep)  
160
    #plot(r2)
161
 
162
    r <- (r1+r2)*0.5  #combine patterns to make a elliptic surface, -0.5 could be modified
163
    #plot(r)
164
  }
116 165

  
117
  #} too slow
166
  #change here to distance from edges..
167
  if(method=="use_edge"){ #does not work with large images
168
      #change...use gdal
169
      n_col <- ncol(r_init)
170
      n_row <- nrow(r_init)
171
    
172
      #xfrom
173
      r_init[1,1:n_col] <- 1
174
      r_init[n_row,1:n_col] <- 1
175
      r_init[1:n_row,1] <- 1
176
      r_init[1:n_row,n_col] <- 1
177
      r_dist <- distance(r_init)
178
      min_val <- cellStats(r_dist,min) 
179
      max_val <- cellStats(r_dist,max)
180
      r <- abs(r_dist - max_val)/ (max_val - min_val)
181
  } #too slow with R use http://www.gdal.org/gdal_proximity.html
118 182
  
119 183
  #plot(r_init)
120
  r_dist <- distance(r_init)
121
  min_val <- cellStats(r_dist,min) 
122
  max_val <- cellStats(r_dist,max)
123
  r_dist_normalized <- abs(r_dist - max_val)/ (max_val - min_val)
184
  
185
  if(method=="use_linear_weights"){
186
    #
187
    r_dist <- distance(r_init)
188
    min_val <- cellStats(r_dist,min) 
189
    max_val <- cellStats(r_dist,max)
190
    r <- abs(r_dist - max_val)/ (max_val - min_val)
191
  }
124 192
  
125 193
  extension_str <- extension(lf[i])
126 194
  raster_name_tmp <- gsub(extension_str,"",basename(lf[i]))
127
  raster_name <- file.path(out_dir_str,paste(raster_name_tmp,"_weights.tif",sep=""))
128
  writeRaster(r_dist_normalized, NAflag=NA_flag_val,filename=raster_name,overwrite=TRUE)  
195
  raster_name <- file.path(out_dir_str,paste(raster_name_tmp,"_",method,"_weights.tif",sep=""))
196
  writeRaster(r, NAflag=NA_flag_val,filename=raster_name,overwrite=TRUE)  
129 197
  
130
  r_var_prod <- r1*r_dist_normalized
131
  raster_name_prod <- file.path(out_dir_str, paste(raster_name_tmp,"_prod_weights.tif",sep=""))
198
  r_var_prod <- r_in*r
199
  raster_name_prod <- file.path(out_dir_str, paste(raster_name_tmp,"_",method,"_prod_weights.tif",sep=""))
132 200
  writeRaster(r_var_prod, NAflag=NA_flag_val,filename=raster_name_prod,overwrite=TRUE)  
133 201
    
134 202
  weights_obj <- list(raster_name,raster_name_prod)
......
244 312

  
245 313
y_var_name <- "dailyTmax" #PARAM1
246 314
interpolation_method <- c("gam_CAI") #PARAM2
247
out_suffix <- "mosaic_run10_1500x4500_global_analyses_05252015" #PARAM3
315
out_suffix <- "mosaic_run10_1500x4500_global_analyses_05272015" #PARAM3
248 316
out_dir <- in_dir #PARAM4
249 317
create_out_dir_param <- TRUE #PARAM 5
250 318

  
......
315 383
out_dir_str <- out_dir
316 384
lf_r_weights <- vector("list",length=length(lf_mosaic))
317 385

  
318
list_param_create_weights <- list(lf_mosaic,df_centroids,out_dir_str) 
319
names(list_param_create_weights) <- c("lf","df_points","out_dir_str") 
386
#methods availbable:use_sine_weights,use_edge,use_linear_weights
387

  
388
### First use linear weights
389
method <- "use_linear_weights"
390
df_points <- df_centroids
391
#df_points <- NULL
392
r_feature <- NULL
393

  
394
list_param_create_weights <- list(lf_mosaic,df_points,r_feature,method,out_dir_str) 
395
names(list_param_create_weights) <- c("lf","df_points","r_feature","method","out_dir_str") 
320 396
num_cores <- 11
321 397

  
322 398
#debug(create_weights_fun)
......
324 400

  
325 401
#This is the function creating the weights by tile. Distance from the centroids needs to be change from distance to
326 402
#the edges...can use rows and columsn to set edges to 1 and 0 for the others.
327
weights_obj_list <- mclapply(1:length(lf_mosaic),FUN=create_weights_fun,list_param=list_param_create_weights,mc.preschedule=FALSE,mc.cores = num_cores)                           
403
linear_weights_obj_list <- mclapply(1:length(lf_mosaic),FUN=create_weights_fun,list_param=list_param_create_weights,mc.preschedule=FALSE,mc.cores = num_cores)                           
404

  
405
list_linear_r_weights <- lapply(1:length(linear_weights_obj_list), FUN=function(i,x){x[[i]]$r_weights},x=linear_weights_obj_list)
406
list_linear_r_weights_prod <- lapply(1:length(linear_weights_obj_list), FUN=function(i,x){x[[i]]$r_weights_prod},x=linear_weights_obj_list)
407

  
408
### Second use sine weights
409
method <- "use_sine_weights"
410
#df_points <- df_centroids
411
df_points <- NULL
412
r_feature <- NULL
413

  
414
list_param_create_weights <- list(lf_mosaic,df_points,r_feature,method,out_dir_str) 
415
names(list_param_create_weights) <- c("lf","df_points","r_feature","method","out_dir_str") 
416
num_cores <- 11
417

  
418
#debug(create_weights_fun)
419
weights_obj <- create_weights_fun(1,list_param=list_param_create_weights)
328 420

  
329
list_r_weights <- lapply(1:length(weights_obj_list), FUN=function(i,x){x[[i]]$r_weights},x=weights_obj_list)
330
list_r_weights_prod <- lapply(1:length(weights_obj_list), FUN=function(i,x){x[[i]]$r_weights_prod},x=weights_obj_list)
421
#This is the function creating the weights by tile. Distance from the centroids needs to be change from distance to
422
#the edges...can use rows and columsn to set edges to 1 and 0 for the others.
423
sine_weights_obj_list <- mclapply(1:length(lf_mosaic),FUN=create_weights_fun,list_param=list_param_create_weights,mc.preschedule=FALSE,mc.cores = num_cores)                           
424

  
425
list_sine_r_weights <- lapply(1:length(sine_weights_obj_list), FUN=function(i,x){x[[i]]$r_weights},x=sine_weights_obj_list)
426
list_sine_r_weights_prod <- lapply(1:length(sine_weights_obj_list), FUN=function(i,x){x[[i]]$r_weights_prod},x=sine_weights_obj_list)
427

  
428
##
331 429

  
332 430
#Then scale on 1 to zero? or 0 to 1000
333 431
#e.g. for a specific pixel
......
337 435
#m_val= sum_val/weight_sum #mean value
338 436
#
339 437

  
438
###############
439
### PART 3: prepare weightsfor mosaicing by matching extent ############
440

  
340 441
## Rasters tiles vary slightly in resolution, they need to be matched for the mosaic. Resolve issue in the 
341 442
#mosaic funciton using gdal_merge to compute a reference image to mach.
342 443
#outrastnames <- "reg1_mosaic_weights.tif"
......
348 449

  
349 450
## Create raster image for original predicted images with matching resolution and extent to the mosaic (reference image)
350 451

  
351
rast_ref <- file.path(out_dir,"avg.tif")
352
lf_files <- unlist(list_r_weights)
452
rast_ref <- file.path(out_dir,"avg.tif") #this is a the ref
453

  
454

  
455
### First match weights from linear option
456
lf_files <- unlist(list_linear_r_weights)
353 457

  
354 458
list_param_raster_match <- list(lf_files,rast_ref,file_format,out_suffix,out_dir)
355 459
names(list_param_raster_match) <- c("lf_files","rast_ref","file_format","out_suffix","out_dir_str")
......
357 461
#debug(raster_match)
358 462
#r_test <- raster(raster_match(1,list_param_raster_match))
359 463

  
360
list_weights_m <- mclapply(1:length(lf_files),FUN=raster_match,list_param=list_param_raster_match,mc.preschedule=FALSE,mc.cores = num_cores)                           
464
list_linear_weights_m <- mclapply(1:length(lf_files),FUN=raster_match,list_param=list_param_raster_match,mc.preschedule=FALSE,mc.cores = num_cores)                           
361 465

  
362
lf_files <- unlist(list_r_weights_prod)
466
lf_files <- unlist(list_linear_r_weights_prod)
363 467
list_param_raster_match <- list(lf_files,rast_ref,file_format,out_suffix,out_dir)
364 468
names(list_param_raster_match) <- c("lf_files","rast_ref","file_format","out_suffix","out_dir_str")
365 469

  
366 470
num_cores <-11
367
list_weights_prod_m <- mclapply(1:length(lf_files),FUN=raster_match,list_param=list_param_raster_match,mc.preschedule=FALSE,mc.cores = num_cores)                           
471
list_linear_weights_prod_m <- mclapply(1:length(lf_files),FUN=raster_match,list_param=list_param_raster_match,mc.preschedule=FALSE,mc.cores = num_cores)                           
472

  
473

  
474
### Second match wegihts from sine option
475

  
476
lf_files <- unlist(list_sine_r_weights)
477

  
478
list_param_raster_match <- list(lf_files,rast_ref,file_format,out_suffix,out_dir)
479
names(list_param_raster_match) <- c("lf_files","rast_ref","file_format","out_suffix","out_dir_str")
368 480

  
481
#debug(raster_match)
482
#r_test <- raster(raster_match(1,list_param_raster_match))
483

  
484
list_sine_weights_m <- mclapply(1:length(lf_files),FUN=raster_match,list_param=list_param_raster_match,mc.preschedule=FALSE,mc.cores = num_cores)                           
485

  
486
lf_files <- unlist(list_sine_r_weights_prod)
487
list_param_raster_match <- list(lf_files,rast_ref,file_format,out_suffix,out_dir)
488
names(list_param_raster_match) <- c("lf_files","rast_ref","file_format","out_suffix","out_dir_str")
489

  
490
num_cores <-11
491
list_sine_weights_prod_m <- mclapply(1:length(lf_files),FUN=raster_match,list_param=list_param_raster_match,mc.preschedule=FALSE,mc.cores = num_cores)                           
492

  
493
#### Third use original images
369 494
#macth file to mosaic extent using the original predictions
370 495
lf_files <- lf_mosaic
371 496
list_param_raster_match <- list(lf_files,rast_ref,file_format,out_suffix,out_dir)
......
374 499
list_pred_m <- mclapply(1:length(lf_files),FUN=raster_match,list_param=list_param_raster_match,mc.preschedule=FALSE,mc.cores = num_cores)                           
375 500

  
376 501
#####################
377
###### PART 3: compute the weighted mean with the mosaic function #####
502
###### PART 4: compute the weighted mean with the mosaic function #####
378 503

  
379 504
#get the list of weights into raster objects
380
list_args_weights <- list_weights_m
505
list_args_linear_weights <- list_linear_weights_m
506
#list_args_weights <- (mixedsort(list.files(pattern="r_weights_m_.*.tif")))
507
list_args_linear_weights <- lapply(1:length(list_args_linear_weights), FUN=function(i,x){raster(x[[i]])},x=list_args_linear_weights)
508

  
509
#get the list of weights product into raster objects
510
list_args_linear_weights_prod <- list_linear_weights_prod_m
511
#list_args_weights_prod <- (mixedsort(list.files(pattern="r_weights_prod_m_.*.tif")))
512
list_args_linear_weights_prod <- lapply(1:length(list_args_linear_weights_prod), FUN=function(i,x){raster(x[[i]])},x=list_args_linear_weights_prod)
513

  
514
#get the list of sine weights into raster objects
515
list_args_sine_weights <- list_sine_weights_m
381 516
#list_args_weights <- (mixedsort(list.files(pattern="r_weights_m_.*.tif")))
382
list_args_weights <- lapply(1:length(list_args_weights), FUN=function(i,x){raster(x[[i]])},x=list_args_weights)
517
list_args_sine_weights <- lapply(1:length(list_args_sine_weights), FUN=function(i,x){raster(x[[i]])},x=list_args_sine_weights)
383 518

  
384 519
#get the list of weights product into raster objects
385
list_args_weights_prod <- list_weights_prod_m
520
list_args_sine_weights_prod <- list_sine_weights_prod_m
386 521
#list_args_weights_prod <- (mixedsort(list.files(pattern="r_weights_prod_m_.*.tif")))
387
list_args_weights_prod <- lapply(1:length(list_args_weights_prod), FUN=function(i,x){raster(x[[i]])},x=list_args_weights_prod)
522
list_args_sine_weights_prod <- lapply(1:length(list_args_sine_weights_prod), FUN=function(i,x){raster(x[[i]])},x=list_args_sine_weights_prod)
388 523

  
389 524
#get the original predicted image to raster (matched previously to the mosaic extent)
390 525
list_args_pred_m <- list_pred_m
391 526
#list_args_pred_m <- (mixedsort(list.files(pattern="^gam_CAI.*.m_mosaic_run10_1500x4500_global_analyses_03252015.tif")))
392 527
list_args_pred_m <- lapply(1:length(list_args_pred_m), FUN=function(i,x){raster(x[[i]])},x=list_args_pred_m)
393 528

  
394
list_args_weights$fun <- "sum" #we want the sum to compute the weighted mean
395
list_args_weights$na.rm <- TRUE
529
list_args_linear_weights$fun <- "sum" #we want the sum to compute the weighted mean
530
list_args_linear_weights$na.rm <- TRUE
396 531

  
397
list_args_weights_prod$fun <- "sum"
398
list_args_weights_prod$na.rm <- TRUE
532
list_args_linear_weights_prod$fun <- "sum"
533
list_args_linear_weights_prod$na.rm <- TRUE
534

  
535
list_args_sine_weights$fun <- "sum" #we want the sum to compute the weighted mean
536
list_args_sine_weights$na.rm <- TRUE
537

  
538
list_args_sine_weights_prod$fun <- "sum"
539
list_args_sine_weights_prod$na.rm <- TRUE
399 540

  
400 541
list_args_pred_m$fun <- "mean"
401 542
list_args_pred_m$na.rm <- TRUE
402 543

  
403 544
#Mosaic files
404
r_weights_sum <- do.call(mosaic,list_args_weights) #weights sum image mosaiced
405
r_prod_sum <- do.call(mosaic,list_args_weights_prod) #weights sum product image mosacied
545
r_linear_weights_sum <- do.call(mosaic,list_args_linear_weights) #weights sum image mosaiced
546
r_linear_prod_sum <- do.call(mosaic,list_args_linear_weights_prod) #weights sum product image mosacied
547

  
548
r_m_linear_weighted_mean <- r_linear_prod_sum/r_linear_weights_sum #this is the mosaic using weighted mean...
406 549

  
407
r_m_weighted_mean <- r_prod_sum/r_weights_sum #this is the mosaic using weighted mean...
550
r_sine_weights_sum <- do.call(mosaic,list_args_sine_weights) #weights sum image mosaiced
551
r_sine_prod_sum <- do.call(mosaic,list_args_sine_weights_prod) #weights sum product image mosacied
408 552

  
409
raster_name <- file.path(out_dir,paste("r_m_weighted_mean",out_suffix,".tif",sep=""))
410
writeRaster(r_m_weighted_mean, NAflag=NA_flag_val,filename=raster_name,overwrite=TRUE)  
553
r_m_sine_weighted_mean <- r_sine_prod_sum/r_sine_weights_sum #this is the mosaic using weighted mean...
554

  
555
raster_name <- file.path(out_dir,paste("r_m_linear_weighted_mean_",out_suffix,".tif",sep=""))
556
writeRaster(r_m_linear_weighted_mean, NAflag=NA_flag_val,filename=raster_name,overwrite=TRUE)  
557

  
558
raster_name <- file.path(out_dir,paste("r_m_sine_weighted_mean_",out_suffix,".tif",sep=""))
559
writeRaster(r_m_sine_weighted_mean, NAflag=NA_flag_val,filename=raster_name,overwrite=TRUE)  
411 560

  
412 561
r_m_mean <- do.call(mosaic,list_args_pred_m) #this is unweighted mean from the predicted raster
413 562

  
414
raster_name <- file.path(out_dir,paste("r_m_mean",out_suffix,".tif",sep=""))
563
raster_name <- file.path(out_dir,paste("r_m_mean_",out_suffix,".tif",sep=""))
415 564
writeRaster(r_m_mean, NAflag=NA_flag_val,filename=raster_name,overwrite=TRUE)  #unweighted mean
416 565

  
566
r_diff_weighted_mean <- r_m_linear_weighted_mean - r_m_sine_weighted_mean
567
#r_diff_weighted_mean<-r_diff_weighted_meam
568

  
569
r_diff_mean_linear <- r_m_mean - r_m_linear_weighted_mean 
570
r_diff_mean_sine <- r_m_mean - r_m_sine_weighted_mean 
571

  
572
#####################
573
###### PART 5: Now plot of the weighted mean and unweighted mean with the mosaic function #####
574

  
417 575
res_pix <- 1200
418 576
col_mfrow <- 1 
419 577
row_mfrow <- 0.8
......
429 587
col_mfrow <- 1 
430 588
row_mfrow <- 0.8
431 589

  
432
png(filename=paste("Figure2_weigthed_mean_for_region_",region_name,"_",out_suffix,".png",sep=""),
590
png(filename=paste("Figure2_linear_weigthed_mean_for_region_",region_name,"_",out_suffix,".png",sep=""),
433 591
    width=col_mfrow*res_pix,height=row_mfrow*res_pix)
434 592

  
435
plot(r_m_weighted_mean)
593
plot(r_m_linear_weighted_mean )
436 594

  
437 595
dev.off()
438 596

  
597
res_pix <- 1200
598
col_mfrow <- 1 
599
row_mfrow <- 0.8
600

  
601
png(filename=paste("Figure2_sine_weigthed_mean_for_region_",region_name,"_",out_suffix,".png",sep=""),
602
    width=col_mfrow*res_pix,height=row_mfrow*res_pix)
603

  
604
plot(r_m_sine_weighted_mean)
605

  
606
dev.off()
607

  
608
res_pix <- 1200
609
col_mfrow <- 1 
610
row_mfrow <- 0.8
611

  
612
png(filename=paste("Figure2_diff_linear_sine_weigthed_mean_for_region_",region_name,"_",out_suffix,".png",sep=""),
613
    width=col_mfrow*res_pix,height=row_mfrow*res_pix)
614

  
615
plot(r_diff_weighted_mean)
616

  
617
dev.off()
618

  
619
res_pix <- 1200
620
col_mfrow <- 1 
621
row_mfrow <- 0.8
622

  
623
png(filename=paste("Figure2_diff_mean_linear_for_region_",region_name,"_",out_suffix,".png",sep=""),
624
    width=col_mfrow*res_pix,height=row_mfrow*res_pix)
625

  
626
plot(r_diff_mean_linear)
627

  
628
dev.off()
629

  
630
res_pix <- 1200
631
col_mfrow <- 1 
632
row_mfrow <- 0.8
633

  
634
png(filename=paste("Figure2_diff_mean_sine_for_region_",region_name,"_",out_suffix,".png",sep=""),
635
    width=col_mfrow*res_pix,height=row_mfrow*res_pix)
636

  
637
plot(r_diff_mean_sine)
638

  
639
dev.off()
439 640

  
440 641
##################### END OF SCRIPT ######################
441 642

  

Also available in: Unified diff