• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2016 The WebM project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include <assert.h>
12 #include <errno.h>
13 #include <math.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include "vpx/vpx_codec.h"
18 #include "vpx/vpx_integer.h"
19 #include "./y4minput.h"
20 #include "vpx_dsp/ssim.h"
21 #include "vpx_ports/mem.h"
22 
23 static const int64_t cc1 = 26634;        // (64^2*(.01*255)^2
24 static const int64_t cc2 = 239708;       // (64^2*(.03*255)^2
25 static const int64_t cc1_10 = 428658;    // (64^2*(.01*1023)^2
26 static const int64_t cc2_10 = 3857925;   // (64^2*(.03*1023)^2
27 static const int64_t cc1_12 = 6868593;   // (64^2*(.01*4095)^2
28 static const int64_t cc2_12 = 61817334;  // (64^2*(.03*4095)^2
29 
30 #if CONFIG_VP9_HIGHBITDEPTH
calc_plane_error16(uint16_t * orig,int orig_stride,uint16_t * recon,int recon_stride,unsigned int cols,unsigned int rows)31 static uint64_t calc_plane_error16(uint16_t *orig, int orig_stride,
32                                    uint16_t *recon, int recon_stride,
33                                    unsigned int cols, unsigned int rows) {
34   unsigned int row, col;
35   uint64_t total_sse = 0;
36   int diff;
37   if (orig == NULL || recon == NULL) {
38     assert(0);
39     return 0;
40   }
41 
42   for (row = 0; row < rows; row++) {
43     for (col = 0; col < cols; col++) {
44       diff = orig[col] - recon[col];
45       total_sse += diff * diff;
46     }
47 
48     orig += orig_stride;
49     recon += recon_stride;
50   }
51   return total_sse;
52 }
53 #endif  // CONFIG_VP9_HIGHBITDEPTH
54 
calc_plane_error(uint8_t * orig,int orig_stride,uint8_t * recon,int recon_stride,unsigned int cols,unsigned int rows)55 static uint64_t calc_plane_error(uint8_t *orig, int orig_stride, uint8_t *recon,
56                                  int recon_stride, unsigned int cols,
57                                  unsigned int rows) {
58   unsigned int row, col;
59   uint64_t total_sse = 0;
60   int diff;
61   if (orig == NULL || recon == NULL) {
62     assert(0);
63     return 0;
64   }
65 
66   for (row = 0; row < rows; row++) {
67     for (col = 0; col < cols; col++) {
68       diff = orig[col] - recon[col];
69       total_sse += diff * diff;
70     }
71 
72     orig += orig_stride;
73     recon += recon_stride;
74   }
75   return total_sse;
76 }
77 
78 #define MAX_PSNR 100
mse2psnr(double samples,double peak,double mse)79 static double mse2psnr(double samples, double peak, double mse) {
80   double psnr;
81 
82   if (mse > 0.0)
83     psnr = 10.0 * log10(peak * peak * samples / mse);
84   else
85     psnr = MAX_PSNR;  // Limit to prevent / 0
86 
87   if (psnr > MAX_PSNR) psnr = MAX_PSNR;
88 
89   return psnr;
90 }
91 
92 typedef enum { RAW_YUV, Y4M } input_file_type;
93 
94 typedef struct input_file {
95   FILE *file;
96   input_file_type type;
97   unsigned char *buf;
98   y4m_input y4m;
99   vpx_image_t img;
100   int w;
101   int h;
102   int bit_depth;
103   int frame_size;
104 } input_file_t;
105 
106 // Open a file and determine if its y4m or raw.  If y4m get the header.
open_input_file(const char * file_name,input_file_t * input,int w,int h,int bit_depth)107 static int open_input_file(const char *file_name, input_file_t *input, int w,
108                            int h, int bit_depth) {
109   char y4m_buf[4];
110   size_t r1;
111   input->w = w;
112   input->h = h;
113   input->bit_depth = bit_depth;
114   input->type = RAW_YUV;
115   input->buf = NULL;
116   input->file = strcmp(file_name, "-") ? fopen(file_name, "rb") : stdin;
117   if (input->file == NULL) return -1;
118   r1 = fread(y4m_buf, 1, 4, input->file);
119   if (r1 == 4) {
120     if (memcmp(y4m_buf, "YUV4", 4) == 0) input->type = Y4M;
121     switch (input->type) {
122       case Y4M:
123         y4m_input_open(&input->y4m, input->file, y4m_buf, 4, 0);
124         input->w = input->y4m.pic_w;
125         input->h = input->y4m.pic_h;
126         input->bit_depth = input->y4m.bit_depth;
127         // Y4M alloc's its own buf. Init this to avoid problems if we never
128         // read frames.
129         memset(&input->img, 0, sizeof(input->img));
130         break;
131       case RAW_YUV:
132         fseek(input->file, 0, SEEK_SET);
133         input->w = w;
134         input->h = h;
135         // handle odd frame sizes
136         input->frame_size = w * h + ((w + 1) / 2) * ((h + 1) / 2) * 2;
137         if (bit_depth > 8) {
138           input->frame_size *= 2;
139         }
140         input->buf = malloc(input->frame_size);
141         break;
142     }
143   }
144   return 0;
145 }
146 
close_input_file(input_file_t * in)147 static void close_input_file(input_file_t *in) {
148   if (in->file) fclose(in->file);
149   if (in->type == Y4M) {
150     vpx_img_free(&in->img);
151   } else {
152     free(in->buf);
153   }
154 }
155 
read_input_file(input_file_t * in,unsigned char ** y,unsigned char ** u,unsigned char ** v,int bd)156 static size_t read_input_file(input_file_t *in, unsigned char **y,
157                               unsigned char **u, unsigned char **v, int bd) {
158   size_t r1 = 0;
159   switch (in->type) {
160     case Y4M:
161       r1 = y4m_input_fetch_frame(&in->y4m, in->file, &in->img);
162       *y = in->img.planes[0];
163       *u = in->img.planes[1];
164       *v = in->img.planes[2];
165       break;
166     case RAW_YUV:
167       if (bd < 9) {
168         r1 = fread(in->buf, in->frame_size, 1, in->file);
169         *y = in->buf;
170         *u = in->buf + in->w * in->h;
171         *v = *u + ((1 + in->w) / 2) * ((1 + in->h) / 2);
172       } else {
173         r1 = fread(in->buf, in->frame_size, 1, in->file);
174         *y = in->buf;
175         *u = in->buf + (in->w * in->h) * 2;
176         *v = *u + 2 * ((1 + in->w) / 2) * ((1 + in->h) / 2);
177       }
178       break;
179   }
180 
181   return r1;
182 }
183 
ssim_parms_8x8(const uint8_t * s,int sp,const uint8_t * r,int rp,uint32_t * sum_s,uint32_t * sum_r,uint32_t * sum_sq_s,uint32_t * sum_sq_r,uint32_t * sum_sxr)184 static void ssim_parms_8x8(const uint8_t *s, int sp, const uint8_t *r, int rp,
185                            uint32_t *sum_s, uint32_t *sum_r, uint32_t *sum_sq_s,
186                            uint32_t *sum_sq_r, uint32_t *sum_sxr) {
187   int i, j;
188   if (s == NULL || r == NULL || sum_s == NULL || sum_r == NULL ||
189       sum_sq_s == NULL || sum_sq_r == NULL || sum_sxr == NULL) {
190     assert(0);
191     return;
192   }
193   for (i = 0; i < 8; i++, s += sp, r += rp) {
194     for (j = 0; j < 8; j++) {
195       *sum_s += s[j];
196       *sum_r += r[j];
197       *sum_sq_s += s[j] * s[j];
198       *sum_sq_r += r[j] * r[j];
199       *sum_sxr += s[j] * r[j];
200     }
201   }
202 }
203 
204 #if CONFIG_VP9_HIGHBITDEPTH
highbd_ssim_parms_8x8(const uint16_t * s,int sp,const uint16_t * r,int rp,uint32_t * sum_s,uint32_t * sum_r,uint32_t * sum_sq_s,uint32_t * sum_sq_r,uint32_t * sum_sxr)205 static void highbd_ssim_parms_8x8(const uint16_t *s, int sp, const uint16_t *r,
206                                   int rp, uint32_t *sum_s, uint32_t *sum_r,
207                                   uint32_t *sum_sq_s, uint32_t *sum_sq_r,
208                                   uint32_t *sum_sxr) {
209   int i, j;
210   if (s == NULL || r == NULL || sum_s == NULL || sum_r == NULL ||
211       sum_sq_s == NULL || sum_sq_r == NULL || sum_sxr == NULL) {
212     assert(0);
213     return;
214   }
215   for (i = 0; i < 8; i++, s += sp, r += rp) {
216     for (j = 0; j < 8; j++) {
217       *sum_s += s[j];
218       *sum_r += r[j];
219       *sum_sq_s += s[j] * s[j];
220       *sum_sq_r += r[j] * r[j];
221       *sum_sxr += s[j] * r[j];
222     }
223   }
224 }
225 #endif  // CONFIG_VP9_HIGHBITDEPTH
226 
similarity(uint32_t sum_s,uint32_t sum_r,uint32_t sum_sq_s,uint32_t sum_sq_r,uint32_t sum_sxr,int count,uint32_t bd)227 static double similarity(uint32_t sum_s, uint32_t sum_r, uint32_t sum_sq_s,
228                          uint32_t sum_sq_r, uint32_t sum_sxr, int count,
229                          uint32_t bd) {
230   double ssim_n, ssim_d;
231   int64_t c1 = 0, c2 = 0;
232   if (bd == 8) {
233     // scale the constants by number of pixels
234     c1 = (cc1 * count * count) >> 12;
235     c2 = (cc2 * count * count) >> 12;
236   } else if (bd == 10) {
237     c1 = (cc1_10 * count * count) >> 12;
238     c2 = (cc2_10 * count * count) >> 12;
239   } else if (bd == 12) {
240     c1 = (cc1_12 * count * count) >> 12;
241     c2 = (cc2_12 * count * count) >> 12;
242   } else {
243     assert(0);
244   }
245 
246   ssim_n = (2.0 * sum_s * sum_r + c1) *
247            (2.0 * count * sum_sxr - 2.0 * sum_s * sum_r + c2);
248 
249   ssim_d = ((double)sum_s * sum_s + (double)sum_r * sum_r + c1) *
250            ((double)count * sum_sq_s - (double)sum_s * sum_s +
251             (double)count * sum_sq_r - (double)sum_r * sum_r + c2);
252 
253   return ssim_n / ssim_d;
254 }
255 
ssim_8x8(const uint8_t * s,int sp,const uint8_t * r,int rp)256 static double ssim_8x8(const uint8_t *s, int sp, const uint8_t *r, int rp) {
257   uint32_t sum_s = 0, sum_r = 0, sum_sq_s = 0, sum_sq_r = 0, sum_sxr = 0;
258   ssim_parms_8x8(s, sp, r, rp, &sum_s, &sum_r, &sum_sq_s, &sum_sq_r, &sum_sxr);
259   return similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64, 8);
260 }
261 
262 #if CONFIG_VP9_HIGHBITDEPTH
highbd_ssim_8x8(const uint16_t * s,int sp,const uint16_t * r,int rp,uint32_t bd)263 static double highbd_ssim_8x8(const uint16_t *s, int sp, const uint16_t *r,
264                               int rp, uint32_t bd) {
265   uint32_t sum_s = 0, sum_r = 0, sum_sq_s = 0, sum_sq_r = 0, sum_sxr = 0;
266   highbd_ssim_parms_8x8(s, sp, r, rp, &sum_s, &sum_r, &sum_sq_s, &sum_sq_r,
267                         &sum_sxr);
268   return similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64, bd);
269 }
270 #endif  // CONFIG_VP9_HIGHBITDEPTH
271 
272 // We are using a 8x8 moving window with starting location of each 8x8 window
273 // on the 4x4 pixel grid. Such arrangement allows the windows to overlap
274 // block boundaries to penalize blocking artifacts.
ssim2(const uint8_t * img1,const uint8_t * img2,int stride_img1,int stride_img2,int width,int height)275 static double ssim2(const uint8_t *img1, const uint8_t *img2, int stride_img1,
276                     int stride_img2, int width, int height) {
277   int i, j;
278   int samples = 0;
279   double ssim_total = 0;
280 
281   // sample point start with each 4x4 location
282   for (i = 0; i <= height - 8;
283        i += 4, img1 += stride_img1 * 4, img2 += stride_img2 * 4) {
284     for (j = 0; j <= width - 8; j += 4) {
285       double v = ssim_8x8(img1 + j, stride_img1, img2 + j, stride_img2);
286       ssim_total += v;
287       samples++;
288     }
289   }
290   ssim_total /= samples;
291   return ssim_total;
292 }
293 
294 #if CONFIG_VP9_HIGHBITDEPTH
highbd_ssim2(const uint8_t * img1,const uint8_t * img2,int stride_img1,int stride_img2,int width,int height,uint32_t bd)295 static double highbd_ssim2(const uint8_t *img1, const uint8_t *img2,
296                            int stride_img1, int stride_img2, int width,
297                            int height, uint32_t bd) {
298   int i, j;
299   int samples = 0;
300   double ssim_total = 0;
301 
302   // sample point start with each 4x4 location
303   for (i = 0; i <= height - 8;
304        i += 4, img1 += stride_img1 * 4, img2 += stride_img2 * 4) {
305     for (j = 0; j <= width - 8; j += 4) {
306       double v =
307           highbd_ssim_8x8(CONVERT_TO_SHORTPTR(img1 + j), stride_img1,
308                           CONVERT_TO_SHORTPTR(img2 + j), stride_img2, bd);
309       ssim_total += v;
310       samples++;
311     }
312   }
313   ssim_total /= samples;
314   return ssim_total;
315 }
316 #endif  // CONFIG_VP9_HIGHBITDEPTH
317 
main(int argc,char * argv[])318 int main(int argc, char *argv[]) {
319   FILE *framestats = NULL;
320   int bit_depth = 8;
321   int w = 0, h = 0, tl_skip = 0, tl_skips_remaining = 0;
322   double ssimavg = 0, ssimyavg = 0, ssimuavg = 0, ssimvavg = 0;
323   double psnrglb = 0, psnryglb = 0, psnruglb = 0, psnrvglb = 0;
324   double psnravg = 0, psnryavg = 0, psnruavg = 0, psnrvavg = 0;
325   double *ssimy = NULL, *ssimu = NULL, *ssimv = NULL;
326   uint64_t *psnry = NULL, *psnru = NULL, *psnrv = NULL;
327   size_t i, n_frames = 0, allocated_frames = 0;
328   int return_value = 0;
329   input_file_t in[2];
330   double peak = 255.0;
331 
332   if (argc < 2) {
333     fprintf(stderr,
334             "Usage: %s file1.{yuv|y4m} file2.{yuv|y4m}"
335             "[WxH tl_skip={0,1,3} frame_stats_file bits]\n",
336             argv[0]);
337     return_value = 1;
338     goto clean_up;
339   }
340 
341   if (argc > 3) {
342     sscanf(argv[3], "%dx%d", &w, &h);
343   }
344 
345   if (argc > 6) {
346     sscanf(argv[6], "%d", &bit_depth);
347   }
348 
349   if (open_input_file(argv[1], &in[0], w, h, bit_depth) < 0) {
350     fprintf(stderr, "File %s can't be opened or parsed!\n", argv[1]);
351     goto clean_up;
352   }
353 
354   if (w == 0 && h == 0) {
355     // If a y4m is the first file and w, h is not set grab from first file.
356     w = in[0].w;
357     h = in[0].h;
358     bit_depth = in[0].bit_depth;
359   }
360   if (bit_depth == 10) peak = 1023.0;
361 
362   if (bit_depth == 12) peak = 4095.0;
363 
364   if (open_input_file(argv[2], &in[1], w, h, bit_depth) < 0) {
365     fprintf(stderr, "File %s can't be opened or parsed!\n", argv[2]);
366     goto clean_up;
367   }
368 
369   if (in[0].w != in[1].w || in[0].h != in[1].h || in[0].w != w ||
370       in[0].h != h || w == 0 || h == 0) {
371     fprintf(stderr,
372             "Failing: Image dimensions don't match or are unspecified!\n");
373     return_value = 1;
374     goto clean_up;
375   }
376 
377   if (in[0].bit_depth != in[1].bit_depth) {
378     fprintf(stderr,
379             "Failing: Image bit depths don't match or are unspecified!\n");
380     return_value = 1;
381     goto clean_up;
382   }
383 
384   bit_depth = in[0].bit_depth;
385 
386   // Number of frames to skip from file1.yuv for every frame used. Normal
387   // values 0, 1 and 3 correspond to TL2, TL1 and TL0 respectively for a 3TL
388   // encoding in mode 10. 7 would be reasonable for comparing TL0 of a 4-layer
389   // encoding.
390   if (argc > 4) {
391     sscanf(argv[4], "%d", &tl_skip);
392     if (argc > 5) {
393       framestats = fopen(argv[5], "w");
394       if (!framestats) {
395         fprintf(stderr, "Could not open \"%s\" for writing: %s\n", argv[5],
396                 strerror(errno));
397         return_value = 1;
398         goto clean_up;
399       }
400     }
401   }
402 
403   while (1) {
404     size_t r1, r2;
405     unsigned char *y[2], *u[2], *v[2];
406 
407     r1 = read_input_file(&in[0], &y[0], &u[0], &v[0], bit_depth);
408 
409     if (r1) {
410       // Reading parts of file1.yuv that were not used in temporal layer.
411       if (tl_skips_remaining > 0) {
412         --tl_skips_remaining;
413         continue;
414       }
415       // Use frame, but skip |tl_skip| after it.
416       tl_skips_remaining = tl_skip;
417     }
418 
419     r2 = read_input_file(&in[1], &y[1], &u[1], &v[1], bit_depth);
420 
421     if (r1 && r2 && r1 != r2) {
422       fprintf(stderr, "Failed to read data: %s [%d/%d]\n", strerror(errno),
423               (int)r1, (int)r2);
424       return_value = 1;
425       goto clean_up;
426     } else if (r1 == 0 || r2 == 0) {
427       break;
428     }
429 #if CONFIG_VP9_HIGHBITDEPTH
430 #define psnr_and_ssim(ssim, psnr, buf0, buf1, w, h)                            \
431   if (bit_depth < 9) {                                                         \
432     ssim = ssim2(buf0, buf1, w, w, w, h);                                      \
433     psnr = calc_plane_error(buf0, w, buf1, w, w, h);                           \
434   } else {                                                                     \
435     ssim = highbd_ssim2(CONVERT_TO_BYTEPTR(buf0), CONVERT_TO_BYTEPTR(buf1), w, \
436                         w, w, h, bit_depth);                                   \
437     psnr = calc_plane_error16(CAST_TO_SHORTPTR(buf0), w,                       \
438                               CAST_TO_SHORTPTR(buf1), w, w, h);                \
439   }
440 #else
441 #define psnr_and_ssim(ssim, psnr, buf0, buf1, w, h) \
442   ssim = ssim2(buf0, buf1, w, w, w, h);             \
443   psnr = calc_plane_error(buf0, w, buf1, w, w, h);
444 #endif  // CONFIG_VP9_HIGHBITDEPTH
445 
446     if (n_frames == allocated_frames) {
447       allocated_frames = allocated_frames == 0 ? 1024 : allocated_frames * 2;
448       ssimy = realloc(ssimy, allocated_frames * sizeof(*ssimy));
449       ssimu = realloc(ssimu, allocated_frames * sizeof(*ssimu));
450       ssimv = realloc(ssimv, allocated_frames * sizeof(*ssimv));
451       psnry = realloc(psnry, allocated_frames * sizeof(*psnry));
452       psnru = realloc(psnru, allocated_frames * sizeof(*psnru));
453       psnrv = realloc(psnrv, allocated_frames * sizeof(*psnrv));
454     }
455     psnr_and_ssim(ssimy[n_frames], psnry[n_frames], y[0], y[1], w, h);
456     psnr_and_ssim(ssimu[n_frames], psnru[n_frames], u[0], u[1], (w + 1) / 2,
457                   (h + 1) / 2);
458     psnr_and_ssim(ssimv[n_frames], psnrv[n_frames], v[0], v[1], (w + 1) / 2,
459                   (h + 1) / 2);
460 
461     n_frames++;
462   }
463 
464   if (framestats) {
465     fprintf(framestats,
466             "ssim,ssim-y,ssim-u,ssim-v,psnr,psnr-y,psnr-u,psnr-v\n");
467   }
468 
469   for (i = 0; i < n_frames; ++i) {
470     double frame_ssim;
471     double frame_psnr, frame_psnry, frame_psnru, frame_psnrv;
472 
473     frame_ssim = 0.8 * ssimy[i] + 0.1 * (ssimu[i] + ssimv[i]);
474     ssimavg += frame_ssim;
475     ssimyavg += ssimy[i];
476     ssimuavg += ssimu[i];
477     ssimvavg += ssimv[i];
478 
479     frame_psnr =
480         mse2psnr(w * h * 6 / 4, peak, (double)psnry[i] + psnru[i] + psnrv[i]);
481     frame_psnry = mse2psnr(w * h * 4 / 4, peak, (double)psnry[i]);
482     frame_psnru = mse2psnr(w * h * 1 / 4, peak, (double)psnru[i]);
483     frame_psnrv = mse2psnr(w * h * 1 / 4, peak, (double)psnrv[i]);
484 
485     psnravg += frame_psnr;
486     psnryavg += frame_psnry;
487     psnruavg += frame_psnru;
488     psnrvavg += frame_psnrv;
489 
490     psnryglb += psnry[i];
491     psnruglb += psnru[i];
492     psnrvglb += psnrv[i];
493 
494     if (framestats) {
495       fprintf(framestats, "%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf\n", frame_ssim,
496               ssimy[i], ssimu[i], ssimv[i], frame_psnr, frame_psnry,
497               frame_psnru, frame_psnrv);
498     }
499   }
500 
501   ssimavg /= n_frames;
502   ssimyavg /= n_frames;
503   ssimuavg /= n_frames;
504   ssimvavg /= n_frames;
505 
506   printf("VpxSSIM: %lf\n", 100 * pow(ssimavg, 8.0));
507   printf("SSIM: %lf\n", ssimavg);
508   printf("SSIM-Y: %lf\n", ssimyavg);
509   printf("SSIM-U: %lf\n", ssimuavg);
510   printf("SSIM-V: %lf\n", ssimvavg);
511   puts("");
512 
513   psnravg /= n_frames;
514   psnryavg /= n_frames;
515   psnruavg /= n_frames;
516   psnrvavg /= n_frames;
517 
518   printf("AvgPSNR: %lf\n", psnravg);
519   printf("AvgPSNR-Y: %lf\n", psnryavg);
520   printf("AvgPSNR-U: %lf\n", psnruavg);
521   printf("AvgPSNR-V: %lf\n", psnrvavg);
522   puts("");
523 
524   psnrglb = psnryglb + psnruglb + psnrvglb;
525   psnrglb = mse2psnr((double)n_frames * w * h * 6 / 4, peak, psnrglb);
526   psnryglb = mse2psnr((double)n_frames * w * h * 4 / 4, peak, psnryglb);
527   psnruglb = mse2psnr((double)n_frames * w * h * 1 / 4, peak, psnruglb);
528   psnrvglb = mse2psnr((double)n_frames * w * h * 1 / 4, peak, psnrvglb);
529 
530   printf("GlbPSNR: %lf\n", psnrglb);
531   printf("GlbPSNR-Y: %lf\n", psnryglb);
532   printf("GlbPSNR-U: %lf\n", psnruglb);
533   printf("GlbPSNR-V: %lf\n", psnrvglb);
534   puts("");
535 
536   printf("Nframes: %d\n", (int)n_frames);
537 
538 clean_up:
539 
540   close_input_file(&in[0]);
541   close_input_file(&in[1]);
542 
543   if (framestats) fclose(framestats);
544 
545   free(ssimy);
546   free(ssimu);
547   free(ssimv);
548 
549   free(psnry);
550   free(psnru);
551   free(psnrv);
552 
553   return return_value;
554 }
555