Commit 5f5c132f authored by Jean-Marc Valin's avatar Jean-Marc Valin Committed by Jean-Marc Valin

Jointly optimizing deringing and clpf

We now signal joint strengths and use a greedy algorithm for the search.

low-latency, cpu-used=4:

ll4-cdef@2017-03-22T03:42:10.815Z -> ll4-cdef-newsearch-var-header-newlambda-refine4@2017-03-22T15:56:46.471Z

   PSNR | PSNR Cb | PSNR Cr | PSNR HVS |    SSIM | MS SSIM | CIEDE 2000
-0.0792 |  0.3551 |  0.4393 |  -0.0108 | -0.1338 | -0.0141 |     0.1452

Change-Id: I619ae1c7c7d7ec04fe993cabc5773b07c3f5b201
parent 800df032
......@@ -366,8 +366,7 @@ typedef struct {
int send_dq_bit;
#endif // CONFIG_NEW_QUANT
/* deringing gain *per-superblock* */
int8_t dering_gain;
int8_t clpf_strength;
int8_t cdef_strength;
#if CONFIG_DELTA_Q
int current_q_index;
#endif
......
......@@ -24,74 +24,6 @@ int dering_level_table[DERING_STRENGTHS] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 17, 20, 24, 28, 33, 39, 46, 54, 63
};
#ifndef NDEBUG
static int is_sorted(const int *arr, int num) {
int sorted = 1;
while (sorted && num-- > 1) sorted &= arr[num] >= arr[num - 1];
return sorted;
}
#endif
uint32_t levels_to_id(const int lev[DERING_REFINEMENT_LEVELS],
const int str[CLPF_REFINEMENT_LEVELS]) {
uint32_t id = 0;
int i;
assert(is_sorted(lev, DERING_REFINEMENT_LEVELS));
assert(is_sorted(str, CLPF_REFINEMENT_LEVELS));
for (i = 0; i < DERING_REFINEMENT_LEVELS; i++)
id = id * DERING_STRENGTHS + lev[i];
for (i = 0; i < CLPF_REFINEMENT_LEVELS; i++)
id = id * CLPF_STRENGTHS + str[i];
return id;
}
void id_to_levels(int lev[DERING_REFINEMENT_LEVELS],
int str[CLPF_REFINEMENT_LEVELS], uint32_t id) {
int i;
for (i = CLPF_REFINEMENT_LEVELS - 1; i >= 0; i--) {
str[i] = id % CLPF_STRENGTHS;
id /= CLPF_STRENGTHS;
}
for (i = DERING_REFINEMENT_LEVELS - 1; i >= 0; i--) {
lev[i] = id % DERING_STRENGTHS;
id /= DERING_STRENGTHS;
}
// Pack tables
int j;
for (i = j = 1; i < DERING_REFINEMENT_LEVELS && j < DERING_REFINEMENT_LEVELS;
i++)
if (lev[j - 1] == lev[j])
memmove(&lev[j - 1], &lev[j],
(DERING_REFINEMENT_LEVELS - j) * sizeof(*lev));
else
j++;
for (i = j = 1; i < CLPF_REFINEMENT_LEVELS && j < DERING_REFINEMENT_LEVELS;
i++)
if (str[j - 1] == str[j])
memmove(&str[j - 1], &str[j],
(CLPF_REFINEMENT_LEVELS - i) * sizeof(*str));
else
j++;
assert(is_sorted(lev, DERING_REFINEMENT_LEVELS));
assert(is_sorted(str, CLPF_REFINEMENT_LEVELS));
}
void cdef_get_bits(const int *lev, const int *str, int *dering_bits,
int *clpf_bits) {
int i;
*dering_bits = *clpf_bits = 1;
for (i = 1; i < DERING_REFINEMENT_LEVELS; i++)
(*dering_bits) += lev[i] != lev[i - 1];
for (i = 1; i < CLPF_REFINEMENT_LEVELS; i++)
(*clpf_bits) += str[i] != str[i - 1];
*dering_bits = get_msb(*dering_bits);
*clpf_bits = get_msb(*clpf_bits);
}
int sb_all_skip(const AV1_COMMON *const cm, int mi_row, int mi_col) {
int r, c;
int maxc, maxr;
......@@ -212,8 +144,7 @@ static void copy_sb8_16(UNUSED AV1_COMMON *cm, uint16_t *dst, int dstride,
}
void av1_cdef_frame(YV12_BUFFER_CONFIG *frame, AV1_COMMON *cm, MACROBLOCKD *xd,
uint32_t global_level, int clpf_strength_u,
int clpf_strength_v) {
int clpf_strength_u, int clpf_strength_v) {
int r, c;
int sbr, sbc;
int nhsb, nvsb;
......@@ -231,12 +162,11 @@ void av1_cdef_frame(YV12_BUFFER_CONFIG *frame, AV1_COMMON *cm, MACROBLOCKD *xd,
int dering_left;
int coeff_shift = AOMMAX(cm->bit_depth - 8, 0);
int nplanes = 3;
int lev[DERING_REFINEMENT_LEVELS];
int str[CLPF_REFINEMENT_LEVELS];
int *lev;
int chroma_dering =
xd->plane[1].subsampling_x == xd->plane[1].subsampling_y &&
xd->plane[2].subsampling_x == xd->plane[2].subsampling_y;
id_to_levels(lev, str, global_level);
lev = cm->cdef_strengths;
nvsb = (cm->mi_rows + MAX_MIB_SIZE - 1) / MAX_MIB_SIZE;
nhsb = (cm->mi_cols + MAX_MIB_SIZE - 1) / MAX_MIB_SIZE;
av1_setup_dst_planes(xd->plane, frame, 0, 0);
......@@ -277,11 +207,13 @@ void av1_cdef_frame(YV12_BUFFER_CONFIG *frame, AV1_COMMON *cm, MACROBLOCKD *xd,
level = dering_level_table
[lev[cm->mi_grid_visible[MAX_MIB_SIZE * sbr * cm->mi_stride +
MAX_MIB_SIZE * sbc]
->mbmi.dering_gain]];
->mbmi.cdef_strength] /
CLPF_STRENGTHS];
clpf_strength =
str[cm->mi_grid_visible[MAX_MIB_SIZE * sbr * cm->mi_stride +
lev[cm->mi_grid_visible[MAX_MIB_SIZE * sbr * cm->mi_stride +
MAX_MIB_SIZE * sbc]
->mbmi.clpf_strength];
->mbmi.cdef_strength] %
CLPF_STRENGTHS;
clpf_strength += clpf_strength == 3;
curr_row_dering[sbc] = 0;
if ((level == 0 && clpf_strength == 0) ||
......
......@@ -11,15 +11,8 @@
#ifndef AV1_COMMON_DERING_H_
#define AV1_COMMON_DERING_H_
// ceil(log2(DERING_STRENGTHS^DERING_REFINEMENT_LEVELS *
// CLPF_STRENGTHS^CLPF_REFINEMENT_LEVELS))
#define DERING_LEVEL_BITS (22)
#define MAX_DERING_LEVEL (1LL << DERING_LEVEL_BITS)
#define DERING_REFINEMENT_BITS 2
#define DERING_REFINEMENT_LEVELS 4
#define CLPF_REFINEMENT_BITS 1
#define CLPF_REFINEMENT_LEVELS 2
#define CDEF_MAX_STRENGTHS 16
#define CDEF_STRENGTH_BITS 7
#define DERING_STRENGTHS 21
#define CLPF_STRENGTHS 4
......@@ -37,19 +30,11 @@ extern "C" {
extern int dering_level_table[DERING_STRENGTHS];
uint32_t levels_to_id(const int lev[DERING_REFINEMENT_LEVELS],
const int str[CLPF_REFINEMENT_LEVELS]);
void id_to_levels(int lev[DERING_REFINEMENT_LEVELS],
int str[CLPF_REFINEMENT_LEVELS], uint32_t id);
void cdef_get_bits(const int *lev, const int *str, int *dering_bits,
int *clpf_bits);
int sb_all_skip(const AV1_COMMON *const cm, int mi_row, int mi_col);
int sb_compute_dering_list(const AV1_COMMON *const cm, int mi_row, int mi_col,
dering_list *dlist);
void av1_cdef_frame(YV12_BUFFER_CONFIG *frame, AV1_COMMON *cm, MACROBLOCKD *xd,
uint32_t global_level, int clpf_strength_u,
int clpf_strength_v);
int clpf_strength_u, int clpf_strength_v);
void av1_cdef_search(YV12_BUFFER_CONFIG *frame, const YV12_BUFFER_CONFIG *ref,
AV1_COMMON *cm, MACROBLOCKD *xd);
......
......@@ -397,11 +397,9 @@ typedef struct AV1Common {
int mib_size; // Size of the superblock in units of MI blocks
int mib_size_log2; // Log 2 of above.
#if CONFIG_CDEF
uint32_t dering_level;
int dering_lev[DERING_REFINEMENT_LEVELS];
int clpf_str[CLPF_REFINEMENT_LEVELS];
int dering_bits;
int clpf_bits;
int nb_cdef_strengths;
int cdef_strengths[CDEF_MAX_STRENGTHS];
int cdef_bits;
int clpf_strength_u;
int clpf_strength_v;
#endif
......
......@@ -2402,14 +2402,11 @@ static void decode_partition(AV1Decoder *const pbi, MACROBLOCKD *const xd,
if (bsize == BLOCK_64X64) {
#endif
if (!sb_all_skip(cm, mi_row, mi_col)) {
cm->mi_grid_visible[mi_row * cm->mi_stride + mi_col]->mbmi.dering_gain =
aom_read_literal(r, cm->dering_bits, ACCT_STR);
cm->mi_grid_visible[mi_row * cm->mi_stride + mi_col]->mbmi.clpf_strength =
aom_read_literal(r, cm->clpf_bits, ACCT_STR);
cm->mi_grid_visible[mi_row * cm->mi_stride + mi_col]->mbmi.cdef_strength =
aom_read_literal(r, cm->cdef_bits, ACCT_STR);
} else {
cm->mi_grid_visible[mi_row * cm->mi_stride + mi_col]->mbmi.dering_gain =
cm->mi_grid_visible[mi_row * cm->mi_stride + mi_col]
->mbmi.clpf_strength = 0;
cm->mi_grid_visible[mi_row * cm->mi_stride + mi_col]->mbmi.cdef_strength =
0;
}
}
#endif // CONFIG_CDEF
......@@ -2673,11 +2670,14 @@ static void setup_loopfilter(AV1_COMMON *cm, struct aom_read_bit_buffer *rb) {
#if CONFIG_CDEF
static void setup_cdef(AV1_COMMON *cm, struct aom_read_bit_buffer *rb) {
cm->dering_level = aom_rb_read_literal(rb, DERING_LEVEL_BITS);
int i;
cm->cdef_bits = aom_rb_read_literal(rb, 2);
cm->nb_cdef_strengths = 1 << cm->cdef_bits;
for (i = 0; i < cm->nb_cdef_strengths; i++) {
cm->cdef_strengths[i] = aom_rb_read_literal(rb, CDEF_STRENGTH_BITS);
}
cm->clpf_strength_u = aom_rb_read_literal(rb, 2);
cm->clpf_strength_v = aom_rb_read_literal(rb, 2);
id_to_levels(cm->dering_lev, cm->clpf_str, cm->dering_level);
cdef_get_bits(cm->dering_lev, cm->clpf_str, &cm->dering_bits, &cm->clpf_bits);
}
#endif // CONFIG_CDEF
......@@ -4950,10 +4950,9 @@ void av1_decode_frame(AV1Decoder *pbi, const uint8_t *data,
}
#if CONFIG_CDEF
if ((cm->dering_level || cm->clpf_strength_u || cm->clpf_strength_v) &&
!cm->skip_loop_filter) {
av1_cdef_frame(&pbi->cur_buf->buf, cm, &pbi->mb, cm->dering_level,
cm->clpf_strength_u, cm->clpf_strength_v);
if (!cm->skip_loop_filter) {
av1_cdef_frame(&pbi->cur_buf->buf, cm, &pbi->mb, cm->clpf_strength_u,
cm->clpf_strength_v);
}
#endif // CONFIG_CDEF
......
......@@ -2785,14 +2785,10 @@ static void write_modes_sb(AV1_COMP *const cpi, const TileInfo *const tile,
if (bsize == BLOCK_64X64 &&
#endif // CONFIG_EXT_PARTITION
!sb_all_skip(cm, mi_row, mi_col)) {
if (cm->dering_bits)
if (cm->cdef_bits != 0)
aom_write_literal(w, cm->mi_grid_visible[mi_row * cm->mi_stride + mi_col]
->mbmi.dering_gain,
cm->dering_bits);
if (cm->clpf_bits)
aom_write_literal(w, cm->mi_grid_visible[mi_row * cm->mi_stride + mi_col]
->mbmi.clpf_strength,
cm->clpf_bits);
->mbmi.cdef_strength,
cm->cdef_bits);
}
#endif
}
......@@ -3496,7 +3492,11 @@ static void encode_loopfilter(AV1_COMMON *cm, struct aom_write_bit_buffer *wb) {
#if CONFIG_CDEF
static void encode_cdef(const AV1_COMMON *cm, struct aom_write_bit_buffer *wb) {
aom_wb_write_literal(wb, cm->dering_level, DERING_LEVEL_BITS);
int i;
aom_wb_write_literal(wb, cm->cdef_bits, 2);
for (i = 0; i < cm->nb_cdef_strengths; i++) {
aom_wb_write_literal(wb, cm->cdef_strengths[i], CDEF_STRENGTH_BITS);
}
aom_wb_write_literal(wb, cm->clpf_strength_u, 2);
aom_wb_write_literal(wb, cm->clpf_strength_v, 2);
}
......
......@@ -3522,14 +3522,17 @@ static void loopfilter_frame(AV1_COMP *cpi, AV1_COMMON *cm) {
}
#if CONFIG_CDEF
if (is_lossless_requested(&cpi->oxcf)) {
cm->dering_level = cm->clpf_strength_u = cm->clpf_strength_v = 0;
cm->clpf_strength_u = cm->clpf_strength_v = 0;
cm->cdef_bits = 0;
cm->cdef_strengths[0] = 0;
cm->nb_cdef_strengths = 1;
} else {
// Find cm->dering_level, cm->clpf_strength_u and cm->clpf_strength_v
av1_cdef_search(cm->frame_to_show, cpi->Source, cm, xd);
// Apply the filter
av1_cdef_frame(cm->frame_to_show, cm, xd, cm->dering_level,
cm->clpf_strength_u, cm->clpf_strength_v);
av1_cdef_frame(cm->frame_to_show, cm, xd, cm->clpf_strength_u,
cm->clpf_strength_v);
// Pack the clpf chroma strengths into two bits each
cm->clpf_strength_u -= cm->clpf_strength_u == 4;
......
......@@ -20,6 +20,64 @@
#include "av1/encoder/clpf_rdo.h"
#include "av1/encoder/encoder.h"
#define TOTAL_STRENGTHS (DERING_STRENGTHS * CLPF_STRENGTHS)
/* Search for the best strength to add as an option, knowing we
already selected nb_strengths options. */
static uint64_t search_one(int *lev, int nb_strengths,
uint64_t mse[][TOTAL_STRENGTHS], int sb_count) {
uint64_t tot_mse[TOTAL_STRENGTHS];
int i, j;
uint64_t best_tot_mse = (uint64_t)1 << 63;
int best_id = 0;
memset(tot_mse, 0, sizeof(tot_mse));
for (i = 0; i < sb_count; i++) {
int gi;
uint64_t best_mse = (uint64_t)1 << 63;
/* Find best mse among already selected options. */
for (gi = 0; gi < nb_strengths; gi++) {
if (mse[i][lev[gi]] < best_mse) {
best_mse = mse[i][lev[gi]];
}
}
/* Find best mse when adding each possible new option. */
for (j = 0; j < TOTAL_STRENGTHS; j++) {
uint64_t best = best_mse;
if (mse[i][j] < best) best = mse[i][j];
tot_mse[j] += best;
}
}
for (j = 0; j < TOTAL_STRENGTHS; j++) {
if (tot_mse[j] < best_tot_mse) {
best_tot_mse = tot_mse[j];
best_id = j;
}
}
lev[nb_strengths] = best_id;
return best_tot_mse;
}
/* Search for the set of strengths that minimizes mse. */
static uint64_t joint_strength_search(int *best_lev, int nb_strengths,
uint64_t mse[][TOTAL_STRENGTHS],
int sb_count) {
uint64_t best_tot_mse;
int i;
best_tot_mse = (uint64_t)1 << 63;
/* Greedy search: add one strength options at a time. */
for (i = 0; i < nb_strengths; i++) {
best_tot_mse = search_one(best_lev, i, mse, sb_count);
}
/* Trying to refine the greedy search by reconsidering each
already-selected option. */
for (i = 0; i < 4 * nb_strengths; i++) {
int j;
for (j = 0; j < nb_strengths - 1; j++) best_lev[j] = best_lev[j + 1];
best_tot_mse = search_one(best_lev, nb_strengths - 1, mse, sb_count);
}
return best_tot_mse;
}
static double compute_dist(uint16_t *x, int xstride, uint16_t *y, int ystride,
int nhb, int nvb, int coeff_shift) {
int i, j;
......@@ -50,21 +108,24 @@ void av1_cdef_search(YV12_BUFFER_CONFIG *frame, const YV12_BUFFER_CONFIG *ref,
int level;
int dering_count;
int coeff_shift = AOMMAX(cm->bit_depth - 8, 0);
uint64_t best_tot_mse = 0;
uint64_t best_tot_mse = (uint64_t)1 << 63;
uint64_t tot_mse;
int sb_count;
int nvsb = (cm->mi_rows + MAX_MIB_SIZE - 1) / MAX_MIB_SIZE;
int nhsb = (cm->mi_cols + MAX_MIB_SIZE - 1) / MAX_MIB_SIZE;
int *sb_index = aom_malloc(nvsb * nhsb * sizeof(*sb_index));
uint64_t(*mse)[DERING_STRENGTHS][CLPF_STRENGTHS] =
uint64_t(*mse)[DERING_STRENGTHS * CLPF_STRENGTHS] =
aom_malloc(sizeof(*mse) * nvsb * nhsb);
int clpf_damping = 3 + (cm->base_qindex >> 6);
int i;
int lev[DERING_REFINEMENT_LEVELS];
int best_lev[DERING_REFINEMENT_LEVELS];
int str[CLPF_REFINEMENT_LEVELS];
int best_str[CLPF_REFINEMENT_LEVELS];
double lambda = exp(cm->base_qindex / 36.0);
static int log2[] = { 0, 1, 2, 2 };
int best_lev[CDEF_MAX_STRENGTHS];
int nb_strengths;
int nb_strength_bits;
int quantizer;
double lambda;
quantizer =
av1_ac_quant(cm->base_qindex, 0, cm->bit_depth) >> (cm->bit_depth - 8);
lambda = .12 * quantizer * quantizer / 256.;
src = aom_memalign(32, sizeof(*src) * cm->mi_rows * cm->mi_cols * 64);
ref_coeff =
......@@ -143,7 +204,7 @@ void av1_cdef_search(YV12_BUFFER_CONFIG *frame, const YV12_BUFFER_CONFIG *ref,
i + (i == 3), clpf_damping, coeff_shift);
copy_dering_16bit_to_16bit(dst, MAX_MIB_SIZE << bsize[0], tmp_dst,
dlist, dering_count, bsize[0]);
mse[sb_count][gi][i] = (int)compute_dist(
mse[sb_count][gi * CLPF_STRENGTHS + i] = (int)compute_dist(
dst, MAX_MIB_SIZE << bsize[0],
&ref_coeff[(sbr * stride * MAX_MIB_SIZE << bsize[0]) +
(sbc * MAX_MIB_SIZE << bsize[0])],
......@@ -155,85 +216,38 @@ void av1_cdef_search(YV12_BUFFER_CONFIG *frame, const YV12_BUFFER_CONFIG *ref,
sb_count++;
}
}
best_tot_mse = (uint64_t)1 << 63;
int l0;
for (l0 = 0; l0 < DERING_STRENGTHS; l0++) {
int l1;
lev[0] = l0;
for (l1 = l0; l1 < DERING_STRENGTHS; l1++) {
int l2;
lev[1] = l1;
for (l2 = l1; l2 < DERING_STRENGTHS; l2++) {
int l3;
lev[2] = l2;
for (l3 = l2; l3 < DERING_STRENGTHS; l3++) {
int cs0;
lev[3] = l3;
for (cs0 = 0; cs0 < CLPF_STRENGTHS; cs0++) {
int cs1;
str[0] = cs0;
for (cs1 = cs0; cs1 < CLPF_STRENGTHS; cs1++) {
uint64_t tot_mse = 0;
str[1] = cs1;
for (i = 0; i < sb_count; i++) {
int gi;
int cs;
uint64_t best_mse = (uint64_t)1 << 63;
for (gi = 0; gi < DERING_REFINEMENT_LEVELS; gi++) {
for (cs = 0; cs < CLPF_REFINEMENT_LEVELS; cs++) {
if (mse[i][lev[gi]][str[cs]] < best_mse) {
best_mse = mse[i][lev[gi]][str[cs]];
}
}
}
tot_mse += best_mse;
}
// Add the bit cost
int dering_diffs = 0, clpf_diffs = 0;
for (i = 1; i < DERING_REFINEMENT_LEVELS; i++)
dering_diffs += lev[i] != lev[i - 1];
for (i = 1; i < CLPF_REFINEMENT_LEVELS; i++)
clpf_diffs += str[i] != str[i - 1];
tot_mse += (uint64_t)(sb_count * lambda *
(log2[dering_diffs] + log2[clpf_diffs]));
if (tot_mse < best_tot_mse) {
for (i = 0; i < DERING_REFINEMENT_LEVELS; i++)
best_lev[i] = lev[i];
for (i = 0; i < CLPF_REFINEMENT_LEVELS; i++)
best_str[i] = str[i];
best_tot_mse = tot_mse;
}
}
}
}
}
nb_strength_bits = 0;
/* Search for different number of signalling bits. */
for (i = 0; i <= 3; i++) {
nb_strengths = 1 << i;
tot_mse = joint_strength_search(best_lev, nb_strengths, mse, sb_count);
/* Count superblock signalling cost. */
tot_mse += (uint64_t)(sb_count * lambda * i);
/* Count header signalling cost. */
tot_mse += (uint64_t)(nb_strengths * lambda * CDEF_STRENGTH_BITS);
if (tot_mse < best_tot_mse) {
best_tot_mse = tot_mse;
nb_strength_bits = i;
}
}
for (i = 0; i < DERING_REFINEMENT_LEVELS; i++) lev[i] = best_lev[i];
for (i = 0; i < CLPF_REFINEMENT_LEVELS; i++) str[i] = best_str[i];
id_to_levels(lev, str, levels_to_id(lev, str)); // Pack tables
cdef_get_bits(lev, str, &cm->dering_bits, &cm->clpf_bits);
nb_strengths = 1 << nb_strength_bits;
cm->cdef_bits = nb_strength_bits;
cm->nb_cdef_strengths = nb_strengths;
for (i = 0; i < nb_strengths; i++) cm->cdef_strengths[i] = best_lev[i];
for (i = 0; i < sb_count; i++) {
int gi, cs;
int best_gi, best_clpf;
int gi;
int best_gi;
uint64_t best_mse = (uint64_t)1 << 63;
best_gi = best_clpf = 0;
for (gi = 0; gi < (1 << cm->dering_bits); gi++) {
for (cs = 0; cs < (1 << cm->clpf_bits); cs++) {
if (mse[i][lev[gi]][str[cs]] < best_mse) {
best_gi = gi;
best_clpf = cs;
best_mse = mse[i][lev[gi]][str[cs]];
}
best_gi = 0;
for (gi = 0; gi < cm->nb_cdef_strengths; gi++) {
if (mse[i][best_lev[gi]] < best_mse) {
best_gi = gi;
best_mse = mse[i][best_lev[gi]];
}
}
cm->mi_grid_visible[sb_index[i]]->mbmi.dering_gain = best_gi;
cm->mi_grid_visible[sb_index[i]]->mbmi.clpf_strength = best_clpf;
cm->mi_grid_visible[sb_index[i]]->mbmi.cdef_strength = best_gi;
}
aom_free(src);
......@@ -245,5 +259,4 @@ void av1_cdef_search(YV12_BUFFER_CONFIG *frame, const YV12_BUFFER_CONFIG *ref,
AOM_PLANE_U);
av1_clpf_test_plane(cm->frame_to_show, ref, cm, &cm->clpf_strength_v,
AOM_PLANE_V);
cm->dering_level = levels_to_id(best_lev, best_str);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment