### change small_basic_block
import mxnet as mx
import numpy as np

def residual_unit(data, num_filter, name, stride = (1, 1), dim_match = 1, bottle_neck=True, bn_mom=0.9, workspace=256, memonger=False):
    if bottle_neck:
        bn1 = mx.sym.BatchNorm(data=data, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn1')
        act1 = mx.sym.Activation(data=bn1, act_type='relu', name=name + '_relu1')
        conv1 = mx.sym.Convolution(data=act1, num_filter=int(num_filter*0.25), kernel=(1,1), stride=(1,1), pad=(0,0),
                                   no_bias=True, workspace=workspace, name=name + '_conv1')
        bn2 = mx.sym.BatchNorm(data=conv1, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn2')
        act2 = mx.sym.Activation(data=bn2, act_type='relu', name=name + '_relu2')
        conv2 = mx.sym.Convolution(data=act2, num_filter=int(num_filter*0.25), kernel=(3,3), stride=stride, pad=(1,1),
                                   no_bias=True, workspace=workspace, name=name + '_conv2')
        bn3 = mx.sym.BatchNorm(data=conv2, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn3')
        act3 = mx.sym.Activation(data=bn3, act_type='relu', name=name + '_relu3')
        conv3 = mx.sym.Convolution(data=act3, num_filter=num_filter, kernel=(1,1), stride=(1,1), pad=(0,0), no_bias=True,
                                   workspace=workspace, name=name + '_conv3')
        if dim_match:
            shortcut = data
        else:
            shortcut = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True,
                                            workspace=workspace, name=name+'_sc')
        if memonger:
            shortcut._set_attr(mirror_stage='True')
        return conv3 + shortcut
    else:
        bn1 = mx.sym.BatchNorm(data=data, fix_gamma=False, momentum=bn_mom, eps=2e-5, name=name + '_bn1')
        act1 = mx.sym.Activation(data=bn1, act_type='relu', name=name + '_relu1')
        conv1 = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(3,3), stride=stride, pad=(1,1),
                                      no_bias=True, workspace=workspace, name=name + '_conv1')
        bn2 = mx.sym.BatchNorm(data=conv1, fix_gamma=False, momentum=bn_mom, eps=2e-5, name=name + '_bn2')
        act2 = mx.sym.Activation(data=bn2, act_type='relu', name=name + '_relu2')
        conv2 = mx.sym.Convolution(data=act2, num_filter=num_filter, kernel=(3,3), stride=(1,1), pad=(1,1),
                                      no_bias=True, workspace=workspace, name=name + '_conv2')
        if dim_match:
            shortcut = data
        else:
            shortcut = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True,
                                            workspace=workspace, name=name+'_sc')
        if memonger:
            shortcut._set_attr(mirror_stage='True')
        return conv2 + shortcut

def _add_warp_ctc_loss(pred, seq_len, num_label, label):
    """ Adds Symbol.contrib.ctc_loss on top of pred symbol and returns the resulting symbol """
    label = mx.sym.Reshape(data=label, shape=(-1,))
    label = mx.sym.Cast(data=label, dtype='int32')
    return mx.sym.WarpCTC(data=pred, label=label, label_length=num_label, input_length=seq_len)

def _add_mxnet_ctc_loss(pred, seq_len, label):
    """ Adds Symbol.WapCTC on top of pred symbol and returns the resulting symbol """

    arg_shape, out_shapes, aux_shapes = pred.infer_shape(data=(128,3,94,24))
    print(out_shapes)

    pred_ctc = mx.sym.Reshape(data=pred, shape=(-4, seq_len, -1, 0))

    arg_shape, out_shapes, aux_shapes = pred_ctc.infer_shape(data=(128,3,94,24))
    print(out_shapes)

    loss = mx.sym.contrib.ctc_loss(data=pred_ctc, label=label)
    ctc_loss = mx.sym.MakeLoss(loss)

    softmax_class = mx.symbol.SoftmaxActivation(data=pred)
    softmax_loss = mx.sym.MakeLoss(softmax_class)
    softmax_loss = mx.sym.BlockGrad(softmax_loss)
    return mx.sym.Group([softmax_loss, ctc_loss])

def _add_ctc_loss(pred, seq_len, num_label, loss_type):
    """ Adds CTC loss on top of pred symbol and returns the resulting symbol """
    label = mx.sym.Variable('label')
    if loss_type == 'warpctc':
        print("Using WarpCTC Loss")
        sm = _add_warp_ctc_loss(pred, seq_len, num_label, label)
    else:
        print("Using MXNet CTC Loss")
        assert loss_type == 'ctc'
        sm = _add_mxnet_ctc_loss(pred, seq_len, label)
    return sm

def get_symbol(num_classes = 78, loss = 'ctc', seq_len = 24, dtype = 'float32', **kwargs):

    cin = [64, 64, 256]
    cout = [64, 256, 256]

    data = mx.sym.Variable(name = 'data')

    s1_conv1 = mx.sym.Convolution(data = data, num_filter = 64, kernel = (3, 3), stride = (1, 1), 
        pad = (1, 1), name = 'stage1_conv1', dilate = (1, 1))
    # s1_bn1 = mx.sym.BatchNorm(data = s1_conv1, name = 'stage1_bn1')
    # s1_act1 = mx.sym.Activation(data = s1_bn1, act_type = 'relu', name = 'stage1_relu1')
    s1_pool1 = mx.sym.Pooling(data = s1_conv1, global_pool = False, kernel=(3, 3), pool_type = 'max', 
        stride = (1, 1), pad = (1, 1), name = 'stage1_pool1')
    # arg_shape, out_shapes, aux_shapes = s1_pool1.infer_shape(data=(128,3,94,24))  
    s1_block1 = residual_unit(data=s1_pool1, num_filter=64, name='stage1_block1')
    # arg_shape, out_shapes, aux_shapes = s1_block1.infer_shape(data=(128,3,94,24))
    
    # arg_shape, out_shapes, aux_shapes = s1_block1.infer_shape(**data_shape)
    
    # s2_bn1 = mx.sym.BatchNorm(data = s1_block1, name = 'stage2_bn1')
    # s2_act1 = mx.sym.Activation(data = s2_bn1, act_type = 'relu', name = 'stage2_relu1')
    s2_pool1 = mx.sym.Pooling(data = s1_block1, global_pool = False, kernel=(3, 3), pool_type = 'max', 
        stride = (2, 1), pad = (1, 1), name = 'stage2_pool1') 
    s2_block1 = residual_unit(data=s2_pool1, num_filter=256, name='stage2_block1', dim_match=0)   
    # s2_block1 = small_basic_block(data=s2_pool1, num_filter=256, name_conv='stage2_block1')
    # s2_bn2 = mx.sym.BatchNorm(data = s2_block1, name = 'stage2_bn2')
    # s2_act2 = mx.sym.Activation(data = s2_bn2, act_type = 'relu', name = 'stage2_relu2')
    # s2_block2 = small_basic_block(data = s2_block1, num_filter=256, name_conv='stage2_block2')
    s2_block2 = residual_unit(data=s2_block1, num_filter=256, name='stage2_block2', dim_match=1) 

    # arg_shape, out_shapes, aux_shapes = s2_block2.infer_shape(**data_shape)

    s3_bn1 = mx.sym.BatchNorm(data = s2_block2, name = 'stage3_bn1')
    s3_act1 = mx.sym.Activation(data = s3_bn1, act_type = 'relu', name = 'stage3_relu1')
    s3_pool1 = mx.sym.Pooling(data = s3_act1, global_pool = False, kernel=(3, 3), pool_type = 'max', 
        stride = (2, 1), pad = (1, 1), name = 'stage3_pool1')
    s3_drop1 = mx.sym.Dropout(data = s3_pool1, p = 0.5)
    s3_conv1 = mx.sym.Convolution(data = s3_drop1, num_filter = 256, kernel = (5, 1), stride = (1, 1), 
        pad = (2, 0), name = 'stage3_conv1', dilate = (1, 1))

    s3_bn2 = mx.sym.BatchNorm(data = s3_conv1, name = 'stage3_bn2')
    s3_act2 = mx.sym.Activation(data = s3_bn2, act_type = 'relu', name = 'stage3_relu2')
    s3_drop2 = mx.sym.Dropout(data = s3_act2, p = 0.5)
    s3_conv2 = mx.sym.Convolution(data = s3_drop2, num_filter = num_classes + 1, kernel = (1, 13), 
        stride = (1, 1), pad = (0, 6), name = 'stage3_conv2', dilate = (1, 1))
    s3_bn3 = mx.sym.BatchNorm(data = s3_conv2, name = 's3_bn3')
    s3_act3 = mx.sym.Activation(data = s3_bn3, act_type = 'relu', name = 'stage3_relu3')


    conv_last = mx.sym.Convolution(data = s3_act3, num_filter = num_classes + 1, kernel = (1, 1), 
        stride = (1, 1), pad = (0,0), name = 'conv_last', dilate = (1, 1))
    bn_last = mx.sym.BatchNorm(data = conv_last, name = 'bn_last')

    logits_b_c_s = mx.sym.Pooling(data = bn_last, kernel = (1,24), stride = (1, 1),  pool_type = 'avg', 
        pad = (0, 0), name = 'pool_last')

    # arg_shape, out_shapes, aux_shapes = logits_b_c_s.infer_shape(data=(128,3,94,24))
    # print('logits_b_c_s:',out_shapes)

    logits_s_b_c = mx.sym.transpose(data=logits_b_c_s,axes=(2,0,1,3))
    # arg_shape, out_shapes, aux_shapes = logits_s_b_c.infer_shape(data=(128,3,94,24))
    # print('logits_b_s_c:',out_shapes)

    logits = mx.sym.reshape(data=logits_s_b_c, shape=(-3, 0))

    if loss:
        # Training mode, add loss
        return _add_ctc_loss(logits, seq_len, 79, loss)
    else:
        # Inference mode, add softmax
        return mx.sym.softmax(data=logits, name='softmax')
    

if __name__ == '__main__':
    # net = get_symbol(num_classes = 78, loss='ctc')
    net = get_symbol(num_classes = 78, loss='')
    mx.viz.plot_network(net, shape={"data" : (1, 3, 94, 24)}, node_attrs={"shape":'rect',"fixedsize":'false'}).view('lprnet_res')

### change small_basic_block
import mxnet as mx
import numpy as np

def residual_unit(data, num_filter, name, stride = (1, 1), dim_match = 1, bottle_neck=True, bn_mom=0.9, workspace=256, memonger=False):
    if bottle_neck:
        bn1 = mx.sym.BatchNorm(data=data, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn1')
        act1 = mx.sym.Activation(data=bn1, act_type='relu', name=name + '_relu1')
        conv1 = mx.sym.Convolution(data=act1, num_filter=int(num_filter*0.25), kernel=(1,1), stride=(1,1), pad=(0,0),
                                   no_bias=True, workspace=workspace, name=name + '_conv1')
        bn2 = mx.sym.BatchNorm(data=conv1, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn2')
        act2 = mx.sym.Activation(data=bn2, act_type='relu', name=name + '_relu2')
        conv2 = mx.sym.Convolution(data=act2, num_filter=int(num_filter*0.25), kernel=(3,3), stride=stride, pad=(1,1),
                                   no_bias=True, workspace=workspace, name=name + '_conv2')
        bn3 = mx.sym.BatchNorm(data=conv2, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn3')
        act3 = mx.sym.Activation(data=bn3, act_type='relu', name=name + '_relu3')
        conv3 = mx.sym.Convolution(data=act3, num_filter=num_filter, kernel=(1,1), stride=(1,1), pad=(0,0), no_bias=True,
                                   workspace=workspace, name=name + '_conv3')
        if dim_match:
            shortcut = data
        else:
            shortcut = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True,
                                            workspace=workspace, name=name+'_sc')
        if memonger:
            shortcut._set_attr(mirror_stage='True')
        return conv3 + shortcut
    else:
        bn1 = mx.sym.BatchNorm(data=data, fix_gamma=False, momentum=bn_mom, eps=2e-5, name=name + '_bn1')
        act1 = mx.sym.Activation(data=bn1, act_type='relu', name=name + '_relu1')
        conv1 = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(3,3), stride=stride, pad=(1,1),
                                      no_bias=True, workspace=workspace, name=name + '_conv1')
        bn2 = mx.sym.BatchNorm(data=conv1, fix_gamma=False, momentum=bn_mom, eps=2e-5, name=name + '_bn2')
        act2 = mx.sym.Activation(data=bn2, act_type='relu', name=name + '_relu2')
        conv2 = mx.sym.Convolution(data=act2, num_filter=num_filter, kernel=(3,3), stride=(1,1), pad=(1,1),
                                      no_bias=True, workspace=workspace, name=name + '_conv2')
        if dim_match:
            shortcut = data
        else:
            shortcut = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True,
                                            workspace=workspace, name=name+'_sc')
        if memonger:
            shortcut._set_attr(mirror_stage='True')
        return conv2 + shortcut

def _add_warp_ctc_loss(pred, seq_len, num_label, label):
    """ Adds Symbol.contrib.ctc_loss on top of pred symbol and returns the resulting symbol """
    label = mx.sym.Reshape(data=label, shape=(-1,))
    label = mx.sym.Cast(data=label, dtype='int32')
    return mx.sym.WarpCTC(data=pred, label=label, label_length=num_label, input_length=seq_len)

def _add_mxnet_ctc_loss(pred, seq_len, label):
    """ Adds Symbol.WapCTC on top of pred symbol and returns the resulting symbol """

    arg_shape, out_shapes, aux_shapes = pred.infer_shape(data=(128,3,94,24))
    print(out_shapes)

    pred_ctc = mx.sym.Reshape(data=pred, shape=(-4, seq_len, -1, 0))

    arg_shape, out_shapes, aux_shapes = pred_ctc.infer_shape(data=(128,3,94,24))
    print(out_shapes)

    loss = mx.sym.contrib.ctc_loss(data=pred_ctc, label=label)
    ctc_loss = mx.sym.MakeLoss(loss)

    softmax_class = mx.symbol.SoftmaxActivation(data=pred)
    softmax_loss = mx.sym.MakeLoss(softmax_class)
    softmax_loss = mx.sym.BlockGrad(softmax_loss)
    return mx.sym.Group([softmax_loss, ctc_loss])

def _add_ctc_loss(pred, seq_len, num_label, loss_type):
    """ Adds CTC loss on top of pred symbol and returns the resulting symbol """
    label = mx.sym.Variable('label')
    if loss_type == 'warpctc':
        print("Using WarpCTC Loss")
        sm = _add_warp_ctc_loss(pred, seq_len, num_label, label)
    else:
        print("Using MXNet CTC Loss")
        assert loss_type == 'ctc'
        sm = _add_mxnet_ctc_loss(pred, seq_len, label)
    return sm

def get_symbol(num_classes = 78, loss = 'ctc', seq_len = 24, dtype = 'float32', **kwargs):

    cin = [64, 64, 256]
    cout = [64, 256, 256]

    data = mx.sym.Variable(name = 'data')

    s1_conv1 = mx.sym.Convolution(data = data, num_filter = 64, kernel = (3, 3), stride = (1, 1), 
        pad = (1, 1), name = 'stage1_conv1', dilate = (1, 1))
    # s1_bn1 = mx.sym.BatchNorm(data = s1_conv1, name = 'stage1_bn1')
    # s1_act1 = mx.sym.Activation(data = s1_bn1, act_type = 'relu', name = 'stage1_relu1')
    s1_pool1 = mx.sym.Pooling(data = s1_conv1, global_pool = False, kernel=(3, 3), pool_type = 'max', 
        stride = (1, 1), pad = (1, 1), name = 'stage1_pool1')
    # arg_shape, out_shapes, aux_shapes = s1_pool1.infer_shape(data=(128,3,94,24))  
    s1_block1 = residual_unit(data=s1_pool1, num_filter=64, name='stage1_block1')
    # arg_shape, out_shapes, aux_shapes = s1_block1.infer_shape(data=(128,3,94,24))
    
    # arg_shape, out_shapes, aux_shapes = s1_block1.infer_shape(**data_shape)
    
    # s2_bn1 = mx.sym.BatchNorm(data = s1_block1, name = 'stage2_bn1')
    # s2_act1 = mx.sym.Activation(data = s2_bn1, act_type = 'relu', name = 'stage2_relu1')
    s2_pool1 = mx.sym.Pooling(data = s1_block1, global_pool = False, kernel=(3, 3), pool_type = 'max', 
        stride = (2, 1), pad = (1, 1), name = 'stage2_pool1') 
    s2_block1 = residual_unit(data=s2_pool1, num_filter=256, name='stage2_block1', dim_match=0)   
    # s2_block1 = small_basic_block(data=s2_pool1, num_filter=256, name_conv='stage2_block1')
    # s2_bn2 = mx.sym.BatchNorm(data = s2_block1, name = 'stage2_bn2')
    # s2_act2 = mx.sym.Activation(data = s2_bn2, act_type = 'relu', name = 'stage2_relu2')
    # s2_block2 = small_basic_block(data = s2_block1, num_filter=256, name_conv='stage2_block2')
    s2_block2 = residual_unit(data=s2_block1, num_filter=256, name='stage2_block2', dim_match=1) 

    # arg_shape, out_shapes, aux_shapes = s2_block2.infer_shape(**data_shape)

    s3_bn1 = mx.sym.BatchNorm(data = s2_block2, name = 'stage3_bn1')
    s3_act1 = mx.sym.Activation(data = s3_bn1, act_type = 'relu', name = 'stage3_relu1')
    s3_pool1 = mx.sym.Pooling(data = s3_act1, global_pool = False, kernel=(3, 3), pool_type = 'max', 
        stride = (2, 1), pad = (1, 1), name = 'stage3_pool1')
    s3_drop1 = mx.sym.Dropout(data = s3_pool1, p = 0.5)
    s3_conv1 = mx.sym.Convolution(data = s3_drop1, num_filter = 256, kernel = (5, 1), stride = (1, 1), 
        pad = (2, 0), name = 'stage3_conv1', dilate = (1, 1))

    s3_bn2 = mx.sym.BatchNorm(data = s3_conv1, name = 'stage3_bn2')
    s3_act2 = mx.sym.Activation(data = s3_bn2, act_type = 'relu', name = 'stage3_relu2')
    s3_drop2 = mx.sym.Dropout(data = s3_act2, p = 0.5)
    s3_conv2 = mx.sym.Convolution(data = s3_drop2, num_filter = num_classes + 1, kernel = (1, 13), 
        stride = (1, 1), pad = (0, 6), name = 'stage3_conv2', dilate = (1, 1))
    s3_bn3 = mx.sym.BatchNorm(data = s3_conv2, name = 's3_bn3')
    s3_act3 = mx.sym.Activation(data = s3_bn3, act_type = 'relu', name = 'stage3_relu3')


    conv_last = mx.sym.Convolution(data = s3_act3, num_filter = num_classes + 1, kernel = (1, 1), 
        stride = (1, 1), pad = (0,0), name = 'conv_last', dilate = (1, 1))
    bn_last = mx.sym.BatchNorm(data = conv_last, name = 'bn_last')

    logits_b_c_s = mx.sym.Pooling(data = bn_last, kernel = (1,24), stride = (1, 1),  pool_type = 'avg', 
        pad = (0, 0), name = 'pool_last')

    # arg_shape, out_shapes, aux_shapes = logits_b_c_s.infer_shape(data=(128,3,94,24))
    # print('logits_b_c_s:',out_shapes)

    logits_s_b_c = mx.sym.transpose(data=logits_b_c_s,axes=(2,0,1,3))
    # arg_shape, out_shapes, aux_shapes = logits_s_b_c.infer_shape(data=(128,3,94,24))
    # print('logits_b_s_c:',out_shapes)

    logits = mx.sym.reshape(data=logits_s_b_c, shape=(-3, 0))

    if loss:
        # Training mode, add loss
        return _add_ctc_loss(logits, seq_len, 79, loss)
    else:
        # Inference mode, add softmax
        return mx.sym.softmax(data=logits, name='softmax')
    

if __name__ == '__main__':
    # net = get_symbol(num_classes = 78, loss='ctc')
    net = get_symbol(num_classes = 78, loss='')
    mx.viz.plot_network(net, shape={"data" : (1, 3, 94, 24)}, node_attrs={"shape":'rect',"fixedsize":'false'}).view('lprnet_res')

### change small_basic_block Adds Symbol.contrib.ctc_loss on top of pred symbol and returns the resulting symbol Adds Symbol.WapCTC on top of pred symbol and returns the resulting symbol Adds CTC loss on top of pred symbol and returns the resulting symbol # s1_bn1 = mx.sym.BatchNorm(data = s1_conv1, name = 'stage1_bn1') # s1_act1 = mx.sym.Activation(data = s1_bn1, act_type = 'relu', name = 'stage1_relu1') # arg_shape, out_shapes, aux_shapes = s1_pool1.infer_shape(data=(128,3,94,24)) # arg_shape, out_shapes, aux_shapes = s1_block1.infer_shape(data=(128,3,94,24)) # arg_shape, out_shapes, aux_shapes = s1_block1.infer_shape(**data_shape) # s2_bn1 = mx.sym.BatchNorm(data = s1_block1, name = 'stage2_bn1') # s2_act1 = mx.sym.Activation(data = s2_bn1, act_type = 'relu', name = 'stage2_relu1') # s2_block1 = small_basic_block(data=s2_pool1, num_filter=256, name_conv='stage2_block1') # s2_bn2 = mx.sym.BatchNorm(data = s2_block1, name = 'stage2_bn2') # s2_act2 = mx.sym.Activation(data = s2_bn2, act_type = 'relu', name = 'stage2_relu2') # s2_block2 = small_basic_block(data = s2_block1, num_filter=256, name_conv='stage2_block2') # arg_shape, out_shapes, aux_shapes = s2_block2.infer_shape(**data_shape) # arg_shape, out_shapes, aux_shapes = logits_b_c_s.infer_shape(data=(128,3,94,24)) # print('logits_b_c_s:',out_shapes) # arg_shape, out_shapes, aux_shapes = logits_s_b_c.infer_shape(data=(128,3,94,24)) # print('logits_b_s_c:',out_shapes) # Training mode, add loss # Inference mode, add softmax # net = get_symbol(num_classes = 78, loss='ctc')