def Detect(self, request, context):
        """ real time detection task!
        """
        t1 = time.perf_counter()
        anypb = Any()

        height = request.height
        width = request.width

        image = np.frombuffer(request.image, dtype=np.uint8)
        #if image.ndim == 1:
        #        image = image.reshape([height,width])
        if not request.isRaw:
            image = cv2.imdecode(image, cv2.IMREAD_GRAYSCALE)
            height, width = image.shape
            image = image.reshape([height, width])

        # check image size, single-channel gray image
        if 0 == image.size or image.size != height * width:
            logging.error(
                f'image shape is {image.shape}, but height = {height}, width = {width}'
            )
            return thyroidrpc_pb2.ProtoResponse(
                code=WRONG_IMAGE_SHAPE,
                msg=ErrorDict[WRONG_IMAGE_SHAPE],
                data=anypb)
        print(image.shape)
        if image.ndim == 1:
            image = image.reshape([height, width])
        res = pymrcnn.ThyroidAIDoInference(image, height, width)
        nodule_nums = len(res)
        logging.info(f'nodules_nums: {nodule_nums}')

        if 0 == nodule_nums:
            nodules = thyroidrpc_pb2.Nodules(nums=0, nodule=[])
        else:
            nodules_list = []
            for i in range(nodule_nums):
                nodules_list.append(
                    thyroidrpc_pb2.NoduleWithNum(n=i + 1,
                                                 m=0,
                                                 x=res[i][0],
                                                 y=res[i][1],
                                                 w=res[i][2],
                                                 h=res[i][3],
                                                 s=0,
                                                 pos=0))
            nodules = thyroidrpc_pb2.Nodules(nums=nodule_nums,
                                             nodule=nodules_list)
        print(nodules)
        # packing results to any
        anypb.Pack(nodules)
        t2 = time.perf_counter()
        logging.info('tensorrt: {:.2f}'.format((t2 - t1) * 1000))
        return thyroidrpc_pb2.ProtoResponse(code=0, msg='', data=anypb)
示例#2
0
def run():
    # NOTE(gRPC Python Team): .close() is possible on a channel and should be
    # used in circumstances in which the with statement does not fit the needs
    # of the code.
    if (':' in args.server):
        ip = args.server
    else:
        ip = args.server + ':' + args.port
    print(f'connecting server: {ip}')

    with grpc.insecure_channel(ip) as channel:
        stub = thyroidrpc_pb2_grpc.ThyroidaiGrpcStub(channel)

        ################################################
        ### 1. test first stage real time detection  ###
        ################################################
        isRaw = True

        if isRaw:
            img = cv2.imread('./images/190528152340059.png', 0)
            h, w = img.shape
            img = img.tostring()
        else:
            with open('./images/190528152340059.png', 'rb') as f:
                img = f.read()
            h, w = cv2.imread('./images/190528152340059.png', 0).shape

        response = stub.Detect(
            thyroidrpc_pb2.DetectRequest(isRaw=isRaw,
                                         image=img,
                                         height=h,
                                         width=w))
        if (response.code != 0):
            print('error code: {}'.format(response.code))
            print('error message: {}'.format(response.msg))
        else:
            anypb = Any()
            anypb.CopyFrom(response.data)

            nodules = thyroidrpc_pb2.Nodules()
            anypb.Unpack(nodules)

            print(f'nodule number is: {nodules.nums}')
            for node in nodules.nodule:
                print(
                    f'nudule {node.n}: ({node.x}, {node.y}, {node.w}, {node.h})'
                )

        ###########################################
        ### 2. test second stage inference      ###
        ###########################################
        print('----------------------')
        print('Second stage detect')
        print('----------------------')
        print('')

        user_id = '123456'
        scan_id = int(time.time() * 10)
        image_id = 1

        list_nodule = []

        cut = 1
        for node in nodules.nodule:
            nx, ny, nw, nh = (node.x, node.y, node.w, node.h)
            list_nodule = [
                thyroidrpc_pb2.NoduleWithNum(n=1,
                                             m=0,
                                             x=nx,
                                             y=ny,
                                             w=nw,
                                             h=nh,
                                             s=cut,
                                             pos=5)
            ]
            if cut == 1:
                cut = 2
            elif cut == 2:
                cut = 1
            else:
                pass

            response = stub.ClassifyAndTirads(
                thyroidrpc_pb2.CTRequest(uid=user_id,
                                         scanID=scan_id,
                                         imageID=image_id,
                                         isRaw=isRaw,
                                         image=img,
                                         height=h,
                                         width=w,
                                         ppc=160,
                                         nodule=list_nodule))

            if (response.code != 0):
                print('error code: {}'.format(response.code))
                print('error message: {}'.format(response.msg))
            else:
                anypb = Any()
                anypb.CopyFrom(response.data)

                ctRes = thyroidrpc_pb2.CTResponse()
                anypb.Unpack(ctRes)

                print(f'total nodule number: {ctRes.nums}')
                print()

                for ds, nodule, bp, tirads in zip(ctRes.ds, ctRes.nodule,
                                                  ctRes.bp, ctRes.tirads):
                    print(f'nodule index: {ds.n}:')
                    if ds.status != 0:
                        print('nodule {} not detect !'.format(ds.n))
                    else:
                        print('{:>25}: ({}, {}, {}, {})'.format(
                            'nodule x:', nodule.x, nodule.y, nodule.w,
                            nodule.h))
                        print('{:>25}: {}'.format('benign', bp.benign))
                        print('{:>25}: {}'.format('corresponding prob',
                                                  bp.prob))
                        print('{:>25}: {}'.format('constitute',
                                                  tirads.constitute))
                        print('{:>25}: {}'.format('Comet tail quantity',
                                                  tirads.comet))
                        print('{:>25}: {}'.format('Shape', tirads.shape))
                        print('{:>25}: {}'.format('Aspect ratio',
                                                  tirads.ratio))
                        print('{:>25}: {}'.format('Horizontal axis(cm)',
                                                  tirads.hxlen))
                        print('{:>25}: {}'.format('Vertical axis(cm)',
                                                  tirads.vxlen))
                        print('{:>25}: {}'.format('echo level',
                                                  tirads.echo_level))
                        print('{:>25}: {}'.format('border_clear',
                                                  tirads.border_clear))
                        print('{:>25}: {}'.format('calcification',
                                                  tirads.calcification[0]))
                        print('{:>25}: {}'.format('calcification',
                                                  tirads.calcification[1]))
                        print('{:>25}: {}'.format('calcification',
                                                  tirads.calcification[2]))
                        print()

            print('----------------------')
            print('generating report')
            print('----------------------')
            print('')

            response = stub.GenerateReport(
                thyroidrpc_pb2.UserID(uid=user_id, scanID=scan_id))
            if (response.code != 0):
                print('error code: {}'.format(response.code))
                print('error message: {}'.format(response.msg))
            else:
                anypb = Any()
                anypb.CopyFrom(response.data)

                ctRes = thyroidrpc_pb2.CTResponse()
                anypb.Unpack(ctRes)

                print(f'total nodule number: {ctRes.nums}')
                print()

                for ds, nodule, bp, tirads in zip(ctRes.ds, ctRes.nodule,
                                                  ctRes.bp, ctRes.tirads):
                    print(f'nodule index: {ds.n}:')
                    if ds.status != 0:
                        print('nodule {} not detect !'.format(ds.n))
                    else:
                        print('{:>25}: ({}, {}, {}, {})'.format(
                            'nodule x:', nodule.x, nodule.y, nodule.w,
                            nodule.h))
                        print('{:>25}: {}'.format('benign', bp.benign))
                        print('{:>25}: {}'.format('corresponding prob',
                                                  bp.prob))
                        print('{:>25}: {}'.format('constitute',
                                                  tirads.constitute))
                        print('{:>25}: {}'.format('Comet tail quantity',
                                                  tirads.comet))
                        print('{:>25}: {}'.format('Shape', tirads.shape))
                        print('{:>25}: {}'.format('Aspect ratio',
                                                  tirads.ratio))
                        #print('{:>25}: {}'.format('frond-end (cm)', tirads.hxlen[0]))
                        #print('{:>25}: {}'.format('left-right(cm)', tirads.hxlen[1]))
                        print('{:>25}: {}'.format('Horizontal axis(cm)',
                                                  tirads.hxlen))
                        print('{:>25}: {}'.format('Vertical axis(cm)',
                                                  tirads.vxlen))
                        print('{:>25}: {}'.format('pos_extend', nodule.pos))
                        print('{:>25}: {}'.format('echo level',
                                                  tirads.echo_level))
                        print('{:>25}: {}'.format('border_clear',
                                                  tirads.border_clear))
                        print('{:>25}: {}'.format('calcification',
                                                  tirads.calcification[0]))
                        print('{:>25}: {}'.format('calcification',
                                                  tirads.calcification[1]))
                        print('{:>25}: {}'.format('calcification',
                                                  tirads.calcification[2]))
                        print()
示例#3
0
def run():
    # NOTE(gRPC Python Team): .close() is possible on a channel and should be
    # used in circumstances in which the with statement does not fit the needs
    # of the code.
    if (':' in args.server):
        ip = args.server
    else:
        ip = args.server + ':' + args.port
    print(f'connecting server: {ip}')

    with grpc.insecure_channel(ip) as channel:
        stub = thyroidrpc_pb2_grpc.ThyroidaiGrpcStub(channel)

        ################################################
        ### 1. test first stage real time detection  ###
        ################################################
        isRaw = True
        img_path = './images/03.jpg'
        if isRaw:
            img = cv2.imread(img_path, 0)
            cv2.imwrite('input.png', img)
            h, w = img.shape
            img = img.tostring()
        else:
            with open(img_path, 'rb') as f:
                img = f.read()
            h, w = cv2.imread(img_path, 0).shape

        response = stub.Detect(
            thyroidrpc_pb2.DetectRequest(isRaw=isRaw,
                                         image=img,
                                         height=h,
                                         width=w))
        if (response.code != 0):
            print('error code: {}'.format(response.code))
            print('error message: {}'.format(response.msg))
        else:
            anypb = Any()
            anypb.CopyFrom(response.data)

            nodules = thyroidrpc_pb2.Nodules()
            anypb.Unpack(nodules)

            print(f'nodule number is: {nodules.nums}')
            for node in nodules.nodule:
                print(
                    f'nudule {node.n}: ({node.x}, {node.y}, {node.w}, {node.h})'
                )

        ########################################### #
        ##### test image interface          ########
        ############################################
        # isRaw = True
        # request = thyroidrpc_pb2.DetectRequest(isRaw=isRaw, image=img, height=h, width=w)
        # response = stub.ImageOutput(thyroidrpc_pb2.DetectRequestNew(isRaw=isRaw, image=img, height=h, width=w, ppc=80))
        # if (response.code !=0 ):
        #     print('error code: {}'.format(response.code))
        #     print('error message: {}'.format(response.msg))
        # else:
        #     anypb =  Any()
        #     anypb.CopyFrom(response.data)

        #     ImageRequest = thyroidrpc_pb2.ImageRequest()
        #     anypb.Unpack(ImageRequest)

        #     new_img = np.frombuffer(ImageRequest.image, dtype=np.uint8)
        #     new_img = new_img.reshape([h, w])
        #     return_msgs = ImageRequest.msg
        #     for return_msg in return_msgs:

        #         print(return_msg.decode(encoding="utf-8"))
        #         # cv2.imwrite('output.png',new_img)
        #         # cv2.imshow('input', cv2.imread(img_path, 0))
        #         # cv2.imshow('output', new_img)

        #         # cv2.waitKey(0)

        ###########################################
        ### 2. test second stage inference      ###
        ###########################################
        print('----------------------')
        print('Second stage detect')
        print('----------------------')
        print('')

        user_id = '123456'
        scan_id = int(time.time() * 10)
        image_id = 1

        list_nodule = []

        cut = 1
        for node in nodules.nodule:
            """
            # extending bounding box to 1.5
            nx = max(0, int(node.x - 0.25 * node.w))
            ny = max(0, int(node.y - 0.25 * node.h))
            nw = min(w, int(node.w * 1.5))
            nh = min(h, int(node.h * 1.5))
            """
            nx, ny, nw, nh = (node.x, node.y, node.w, node.h)
            list_nodule.append(
                thyroidrpc_pb2.NoduleWithNum(n=node.n,
                                             m=0,
                                             x=nx,
                                             y=ny,
                                             w=nw,
                                             h=nh,
                                             s=cut,
                                             pos=5))
            if cut == 1:
                cut = 2
            elif cut == 2:
                cut = 1
            else:
                pass

        response = stub.ClassifyAndTirads(
            thyroidrpc_pb2.CTRequest(uid=user_id,
                                     scanID=scan_id,
                                     imageID=image_id,
                                     isRaw=isRaw,
                                     image=img,
                                     height=h,
                                     width=w,
                                     ppc=160,
                                     nodule=list_nodule))

        if (response.code != 0):
            print('error code: {}'.format(response.code))
            print('error message: {}'.format(response.msg))
        else:
            anypb = Any()
            anypb.CopyFrom(response.data)

            ctRes = thyroidrpc_pb2.CTResponse()
            anypb.Unpack(ctRes)

            print(f'total nodule number: {ctRes.nums}')
            print()

            for ds, nodule, bp, tirads in zip(ctRes.ds, ctRes.nodule, ctRes.bp,
                                              ctRes.tirads):
                print(f'nodule index: {ds.n}:')
                if ds.status != 0:
                    print('nodule {} not detect !'.format(ds.n))
                else:
                    print('{:>25}: ({}, {}, {}, {})'.format(
                        'nodule x:', nodule.x, nodule.y, nodule.w, nodule.h))
                    print('{:>25}: {}'.format('benign', bp.benign))
                    print('{:>25}: {}'.format('corresponding prob', bp.prob))
                    print('{:>25}: {}'.format('constitute', tirads.constitute))
                    print('{:>25}: {}'.format('Comet tail quantity',
                                              tirads.comet))
                    print('{:>25}: {}'.format('Shape', tirads.shape))
                    print('{:>25}: {}'.format('Aspect ratio', tirads.ratio))
                    print('{:>25}: {}'.format('Horizontal axis(cm)',
                                              tirads.hxlen))
                    print('{:>25}: {}'.format('Vertical axis(cm)',
                                              tirads.vxlen))
                    print('{:>25}: {}'.format('echo level', tirads.echo_level))
                    print('{:>25}: {}'.format('border_clear',
                                              tirads.border_clear))
                    print('{:>25}: {}'.format('calcification',
                                              tirads.calcification[0]))
                    print('{:>25}: {}'.format('calcification',
                                              tirads.calcification[1]))
                    print('{:>25}: {}'.format('calcification',
                                              tirads.calcification[2]))
                    print()

        print('------------------------')
        print('Second stage re-request')
        print('------------------------')
        print('')
        list_nodule = []

        cut = 1
        for node in nodules.nodule:
            # extending bounding box to 1.5
            nx = max(0, int(node.x - 0.25 * node.w))
            ny = max(0, int(node.y - 0.25 * node.h))
            nw = min(w, int(node.w * 1.5))
            nh = min(h, int(node.h * 1.5))

            if node.n == 1:
                list_nodule.append(
                    thyroidrpc_pb2.NoduleWithNum(n=node.n,
                                                 m=0,
                                                 x=nx,
                                                 y=ny,
                                                 w=nw,
                                                 h=nh,
                                                 s=cut,
                                                 pos=5))
            else:
                list_nodule.append(
                    thyroidrpc_pb2.NoduleWithNum(n=node.n + 1,
                                                 m=node.n,
                                                 x=nx,
                                                 y=ny,
                                                 w=nw,
                                                 h=nh,
                                                 s=cut,
                                                 pos=5))

            if cut == 1:
                cut = 2
            elif cut == 2:
                cut = 1
            else:
                pass

        response = stub.ClassifyAndTirads(
            thyroidrpc_pb2.CTRequest(uid=user_id,
                                     scanID=scan_id,
                                     imageID=image_id,
                                     isRaw=isRaw,
                                     image=img,
                                     height=h,
                                     width=w,
                                     ppc=160,
                                     nodule=list_nodule))

        if (response.code != 0):
            print('error code: {}'.format(response.code))
            print('error message: {}'.format(response.msg))
        else:
            anypb = Any()
            anypb.CopyFrom(response.data)

            ctRes = thyroidrpc_pb2.CTResponse()
            anypb.Unpack(ctRes)

            print(f'total nodule number: {ctRes.nums}')
            print()

            for ds, nodule, bp, tirads in zip(ctRes.ds, ctRes.nodule, ctRes.bp,
                                              ctRes.tirads):
                print(f'nodule index: {ds.n}:')
                if ds.status != 0:
                    print('nodule {} not detect !'.format(ds.n))
                else:
                    print('{:>25}: ({}, {}, {}, {})'.format(
                        'nodule x:', nodule.x, nodule.y, nodule.w, nodule.h))
                    print('{:>25}: {}'.format('benign', bp.benign))
                    print('{:>25}: {}'.format('corresponding prob', bp.prob))
                    print('{:>25}: {}'.format('constitute', tirads.constitute))
                    print('{:>25}: {}'.format('Comet tail quantity',
                                              tirads.comet))
                    print('{:>25}: {}'.format('Shape', tirads.shape))
                    print('{:>25}: {}'.format('Aspect ratio', tirads.ratio))
                    print('{:>25}: {}'.format('Horizontal axis(cm)',
                                              tirads.hxlen))
                    print('{:>25}: {}'.format('Vertical axis(cm)',
                                              tirads.vxlen))
                    print('{:>25}: {}'.format('echo level', tirads.echo_level))
                    print('{:>25}: {}'.format('border_clear',
                                              tirads.border_clear))
                    print('{:>25}: {}'.format('calcification',
                                              tirads.calcification[0]))
                    print('{:>25}: {}'.format('calcification',
                                              tirads.calcification[1]))
                    print('{:>25}: {}'.format('calcification',
                                              tirads.calcification[2]))
                    print()

        print('----------------------')
        print('deleting nodule')
        print('----------------------')
        print('')

        response = stub.DeleteNodule(
            thyroidrpc_pb2.DeleteNoduleRequest(uid=user_id,
                                               scanID=scan_id,
                                               imageID=image_id,
                                               noduleID=[3]))
        if (response.code != 0):
            print('error code: {}'.format(response.code))
            print('error message: {}'.format(response.msg))
        else:
            print('deleting ok!')

        print('----------------------')
        print('generating report')
        print('----------------------')
        print('')

        response = stub.GenerateReport(
            thyroidrpc_pb2.UserID(uid=user_id, scanID=scan_id))
        if (response.code != 0):
            print('error code: {}'.format(response.code))
            print('error message: {}'.format(response.msg))
        else:
            anypb = Any()
            anypb.CopyFrom(response.data)

            ctRes = thyroidrpc_pb2.CTResponse()
            anypb.Unpack(ctRes)

            print(f'total nodule number: {ctRes.nums}')
            print()

            for ds, nodule, bp, tirads in zip(ctRes.ds, ctRes.nodule, ctRes.bp,
                                              ctRes.tirads):
                print(f'nodule index: {ds.n}:')
                if ds.status != 0:
                    print('nodule {} not detect !'.format(ds.n))
                else:
                    print('{:>25}: ({}, {}, {}, {})'.format(
                        'nodule x:', nodule.x, nodule.y, nodule.w, nodule.h))
                    print('{:>25}: {}'.format('benign', bp.benign))
                    print('{:>25}: {}'.format('corresponding prob', bp.prob))
                    print('{:>25}: {}'.format('constitute', tirads.constitute))
                    print('{:>25}: {}'.format('Comet tail quantity',
                                              tirads.comet))
                    print('{:>25}: {}'.format('Shape', tirads.shape))
                    print('{:>25}: {}'.format('Aspect ratio', tirads.ratio))
                    print('{:>25}: {}'.format('frond-end (cm)',
                                              tirads.hxlen[0]))
                    print('{:>25}: {}'.format('left-right(cm)',
                                              tirads.hxlen[1]))
                    print('{:>25}: {}'.format('up-down (cm)', tirads.vxlen))
                    print('{:>25}: {}'.format('pos_extend', nodule.pos))
                    print('{:>25}: {}'.format('echo level', tirads.echo_level))
                    print('{:>25}: {}'.format('border_clear',
                                              tirads.border_clear))
                    print('{:>25}: {}'.format('calcification',
                                              tirads.calcification[0]))
                    print('{:>25}: {}'.format('calcification',
                                              tirads.calcification[1]))
                    print('{:>25}: {}'.format('calcification',
                                              tirads.calcification[2]))
                    print()

        print('----------------------')
        print('modify report')
        print('----------------------')
        print('')

        response = stub.ModifyReport(
            thyroidrpc_pb2.ModifyCT(uid=user_id,
                                    scanID=scan_id,
                                    nindex=0,
                                    benign=[2],
                                    prob=[0.91],
                                    constitute=[2],
                                    shape=[0],
                                    echo_level=[3],
                                    border_clear=[0]))
        if (response.code != 0):
            print('error code: {}'.format(response.code))

        response = stub.ModifyReport(
            thyroidrpc_pb2.ModifyCT(uid=user_id,
                                    scanID=scan_id,
                                    nindex=1,
                                    benign=[2],
                                    prob=[0.92],
                                    constitute=[3],
                                    shape=[0],
                                    echo_level=[4],
                                    border_clear=[0]))
        if (response.code != 0):
            print('error code: {}'.format(response.code))

        response = stub.ReadReport(
            thyroidrpc_pb2.UserID(uid=user_id, scanID=scan_id))
        if (response.code != 0):
            print('error code: {}'.format(response.code))
            print('error message: {}'.format(response.msg))
        else:
            anypb = Any()
            anypb.CopyFrom(response.data)

            ctRes = thyroidrpc_pb2.CTResponse()
            anypb.Unpack(ctRes)

            print(f'total nodule number: {ctRes.nums}')
            print()

            for ds, nodule, bp, tirads in zip(ctRes.ds, ctRes.nodule, ctRes.bp,
                                              ctRes.tirads):
                print(f'nodule index: {ds.n}:')
                if ds.status != 0:
                    print('nodule {} not detect !'.format(ds.n))
                else:
                    print('{:>25}: ({}, {}, {}, {})'.format(
                        'nodule x:', nodule.x, nodule.y, nodule.w, nodule.h))
                    print('{:>25}: {}'.format('benign', bp.benign))
                    print('{:>25}: {}'.format('corresponding prob', bp.prob))
                    print('{:>25}: {}'.format('constitute', tirads.constitute))
                    print('{:>25}: {}'.format('Comet tail quantity',
                                              tirads.comet))
                    print('{:>25}: {}'.format('Shape', tirads.shape))
                    print('{:>25}: {}'.format('Aspect ratio', tirads.ratio))
                    print('{:>25}: {}'.format('frond-end (cm)',
                                              tirads.hxlen[0]))
                    print('{:>25}: {}'.format('left-right(cm)',
                                              tirads.hxlen[1]))
                    print('{:>25}: {}'.format('up-down (cm)', tirads.vxlen))
                    print('{:>25}: {}'.format('pos_extend', nodule.pos))
                    print('{:>25}: {}'.format('echo level', tirads.echo_level))
                    print('{:>25}: {}'.format('border_clear',
                                              tirads.border_clear))
                    print('{:>25}: {}'.format('calcification',
                                              tirads.calcification[0]))
                    print('{:>25}: {}'.format('calcification',
                                              tirads.calcification[1]))
                    print('{:>25}: {}'.format('calcification',
                                              tirads.calcification[2]))
                    print()
    def ReadReport(self, request, context):
        """ read final report
        """
        logging.info('---------------------------------------')
        logging.info('ReadReport')
        t1 = time.perf_counter()

        # output list
        list_ds = []
        list_nodule = []
        list_benign_prob = []
        list_tirads = []
        modify_flag = []
        # return mongodb report
        for nodule_report in userdb.report.find({
                'uid': request.uid,
                'scanID': request.scanID
        }):
            modify_flag.append(True)
            #nodule index
            nindex = nodule_report['nodule']

            # status
            list_ds.append(thyroidrpc_pb2.DetectionStatus(n=nindex, status=0))

            # position
            x, y, w, h, = nodule_report['pos']
            s = nodule_report['cut']
            pos_extend = nodule_report['pos_extend']

            list_nodule.append(
                thyroidrpc_pb2.NoduleWithNum(n=nindex,
                                             m=0,
                                             x=x,
                                             y=y,
                                             w=w,
                                             h=h,
                                             s=s,
                                             pos=pos_extend))

            # classification
            benign = nodule_report['benign']
            prob = nodule_report['prob']
            list_benign_prob.append(
                thyroidrpc_pb2.BenignAndProb(benign=benign, prob=prob))

            # tirads
            constitute = nodule_report['constitute']
            comet = nodule_report['comet']
            shape = nodule_report['shape']
            border_clear = nodule_report['border_clear']
            echo_level = nodule_report['echo_level']
            ratio = nodule_report['ratio']
            hxlen = nodule_report['hxlen']
            vxlen = nodule_report['vxlen']
            calcification = nodule_report['calcification']

            list_tirads.append(
                thyroidrpc_pb2.OneTiradsRes(constitute=constitute,
                                            comet=comet,
                                            shape=shape,
                                            ratio=ratio,
                                            hxlen=hxlen,
                                            vxlen=vxlen,
                                            echo_level=echo_level,
                                            border_clear=border_clear,
                                            calcification=calcification))

        nums = userdb.report.count_documents({
            'uid': request.uid,
            'scanID': request.scanID
        })
        anypb = Any()
        anypb.Pack(
            thyroidrpc_pb2.CTResponse(nums=nums,
                                      ds=list_ds,
                                      nodule=list_nodule,
                                      bp=list_benign_prob,
                                      tirads=list_tirads,
                                      modify_flag=modify_flag))

        t2 = time.perf_counter()
        logging.info('read report: {:.2f}'.format((t2 - t1) * 1000))
        return thyroidrpc_pb2.ProtoResponse(code=0, msg='', data=anypb)
    def GenerateReport(self, request, context):
        """generating report function
        """

        logging.info('---------------------------------------')
        logging.info('GenerateReport')
        t1 = time.perf_counter()

        logging.info(f'uid: {request.uid}, scanID: {request.scanID}')

        # we should only process the un-generate report records
        # record has flag *flag_report*
        # at first count module appearing times in all images
        nodule_dict = {}  # key(nodule index):  value(appear times)
        for user in userdb.collection.find({
                'uid': request.uid,
                'scanID': request.scanID,
                'flag_report': False
        }):
            key = user['nodule']
            nodule_dict[key] = nodule_dict.get(key, 0) + 1

        # merge results to one record
        # loop over nodule index
        for index, value in nodule_dict.items():
            logging.info(f'nodule index: {index}, un-gerated records: {value}')
            if value > 1:
                # merge results
                one_record = self._merge_records(request.uid, request.scanID,
                                                 index, value)
            else:
                # set *flag_report* True, means this record has been used in final report!
                one_record = userdb.collection.find_one_and_update(
                    {
                        'uid': request.uid,
                        'scanID': request.scanID,
                        'nodule': index
                    }, {'$set': {
                        'flag_report': True
                    }},
                    return_document=pymongo.ReturnDocument.AFTER)

                del one_record['_id']
                del one_record['imageID']
                del one_record['flag_report']
                one_record[
                    'flag_modified'] = False  # means the doctor does not modify the report!

        # merge one_record and report
            report = userdb.report.find_one({
                'uid': request.uid,
                'scanID': request.scanID,
                'nodule': index
            })

            # if no report, just insert one!
            if report is None:
                userdb.report.insert_one(one_record)
            else:
                self._merge_record_and_report(one_record, report)

        # output list
        list_ds = []
        list_nodule = []
        list_benign_prob = []
        list_tirads = []
        modify_flag = []
        # return mongodb report
        for nodule_report in userdb.report.find({
                'uid': request.uid,
                'scanID': request.scanID
        }):
            # modify_flag
            modify_flag.append(False)

            #nodule index
            nindex = nodule_report['nodule']

            # status
            list_ds.append(thyroidrpc_pb2.DetectionStatus(n=nindex, status=0))

            # position
            x, y, w, h, = nodule_report['pos']
            s = nodule_report['cut']
            pos_extend = nodule_report['pos_extend']

            list_nodule.append(
                thyroidrpc_pb2.NoduleWithNum(n=nindex,
                                             m=0,
                                             x=x,
                                             y=y,
                                             w=w,
                                             h=h,
                                             s=s,
                                             pos=pos_extend))

            # classification
            benign = nodule_report['benign']
            prob = nodule_report['prob']
            list_benign_prob.append(
                thyroidrpc_pb2.BenignAndProb(benign=benign, prob=prob))

            # tirads
            constitute = nodule_report['constitute']
            comet = nodule_report['comet']
            shape = nodule_report['shape']
            border_clear = nodule_report['border_clear']
            echo_level = nodule_report['echo_level']
            ratio = nodule_report['ratio']
            hxlen = nodule_report['hxlen']
            vxlen = nodule_report['vxlen']
            calcification = nodule_report['calcification']

            list_tirads.append(
                thyroidrpc_pb2.OneTiradsRes(constitute=constitute,
                                            comet=comet,
                                            shape=shape,
                                            ratio=ratio,
                                            hxlen=hxlen,
                                            vxlen=vxlen,
                                            echo_level=echo_level,
                                            border_clear=border_clear,
                                            calcification=calcification))

        nums = userdb.report.count_documents({
            'uid': request.uid,
            'scanID': request.scanID
        })
        anypb = Any()
        anypb.Pack(
            thyroidrpc_pb2.CTResponse(nums=nums,
                                      ds=list_ds,
                                      nodule=list_nodule,
                                      bp=list_benign_prob,
                                      tirads=list_tirads,
                                      modify_flag=modify_flag))

        t2 = time.perf_counter()
        logging.info('generate report: {:.2f}'.format((t2 - t1) * 1000))
        return thyroidrpc_pb2.ProtoResponse(code=0, msg='', data=anypb)
    def ClassifyAndTirads(self, request, context):
        """ classification, tirads
        """

        logging.info('---------------------------------------')
        logging.info('ClassifyAndTirads')
        logging.info(
            f'uid: {request.uid}, scanID: {request.scanID}, imageID: {request.imageID}'
        )

        anypb = Any()

        image_gray = np.frombuffer(request.image, dtype=np.uint8)
        height = request.height
        width = request.width

        if not request.isRaw:
            image_gray = cv2.imdecode(image_gray, cv2.IMREAD_GRAYSCALE)
            height, width = image_gray.shape

        # check image size
        if 0 == image_gray.size or image_gray.size != height * width:
            logging.error(
                f'image shape is {image_gray.shape}, but height = {height}, width = {width}'
            )
            return thyroidrpc_pb2.ProtoResponse(
                code=WRONG_IMAGE_SHAPE,
                msg=ErrorDict[WRONG_IMAGE_SHAPE],
                data=anypb)

        if image_gray.ndim == 1:
            image_gray = image_gray.reshape([height, width])

        logging.info(f'image shape is height = {height}, width = {width}')

        # gray to rgb
        image = cv2.cvtColor(image_gray, cv2.COLOR_GRAY2BGR)

        # check input nodule number
        if len(request.nodule) <= 0:
            logging.error(
                f'input nodule numbers should large than zero, but your input is: {len(request.nodule)}'
            )
            return thyroidrpc_pb2.ProtoResponse(
                code=WRONG_INPUT_NODELES,
                msg=ErrorDict[WRONG_INPUT_NODELES],
                data=anypb)
        """
        what's the logic?
        1. loop over request bounding box, use second stage maskrcnn to generate new mask
        2. for each new mask/nodule, do classification task
        3. for each new mask/nodule, do tirads task
        4. save the results for final report
        """

        # output list
        list_ds = []
        list_nodule = []
        list_benign_prob = []
        list_tirads = []
        modify_flag = []

        # loop over all the nodules
        for nodule in request.nodule:
            modify_flag.append(False)
            t1 = time.perf_counter()

            logging.info(
                f'nodule index: {nodule.n}, pos = ({nodule.x}, {nodule.y}, {nodule.w}, {nodule.h})'
            )
            # 1. do second stage segmentation
            bbox_input = (nodule.x, nodule.y, nodule.x + nodule.w,
                          nodule.y + nodule.h)

            try:
                mask, bbox = maskrcnn2seg.do_inference(image_gray, bbox_input)
                print(bbox)
            except:
                logging.error('second stage detection exception')
                return thyroidrpc_pb2.ProtoResponse(
                    code=SECOND_DETECTION_FAIL,
                    msg=ErrorDict[SECOND_DETECTION_FAIL],
                    data=anypb)
            if mask.size == 0:
                logging.error(
                    f'{nodule.n}: second stage not detect any nodule!')
                list_ds.append(
                    thyroidrpc_pb2.DetectionStatus(n=nodule.n, status=1))
                list_nodule.append([])
                list_benign_prob.append([])
                list_tirads.append([])
                continue
            else:
                list_nodule.append(
                    thyroidrpc_pb2.NoduleWithNum(n=nodule.n,
                                                 m=nodule.m,
                                                 x=bbox[0],
                                                 y=bbox[1],
                                                 w=bbox[2] - bbox[0],
                                                 h=bbox[3] - bbox[1],
                                                 s=nodule.s,
                                                 pos=nodule.pos))

            bbox_pos = [
                bbox[0].item(), bbox[1].item(), (bbox[2] - bbox[0]).item(),
                (bbox[3] - bbox[1]).item()
            ]

            t2 = time.perf_counter()
            logging.info('nodule {}, second detection time: {:.2f}'.format(
                nodule.n, (t2 - t1) * 1000))

            # 2. do classification
            t1 = time.perf_counter()

            try:
                benign, prob = rescnn.do_inference(image_gray, mask)
                print(benign, prob)
            except:
                logging.error('classification fail!')
                return thyroidrpc_pb2.ProtoResponse(
                    code=CLASSIFICATION_FAIL,
                    msg=ErrorDict[CLASSIFICATION_FAIL],
                    data=anypb)

            # 1: benign; 2: not benign
            benign += 1
            list_benign_prob.append(
                thyroidrpc_pb2.BenignAndProb(benign=benign, prob=prob))
            t2 = time.perf_counter()
            logging.info('nodule {}, classification time: {:.2f}'.format(
                nodule.n, (t2 - t1) * 1000))

            # 3. do tirads
            """
            constitute 构成  0: 实性    1: 实性为主  2: 囊性为主  3: 囊性
            comet 彗星尾数量, 不过不要显示数量,直接大于1时显示有彗星尾就行
            shape 0: 形状规则  1: 形状不规则
            ar  # 纵横比
            hx_len  # 水平轴 如果是横切,它就是左右径,如果是纵切,它就是上下径
            vx_len  # 垂直轴  它只有可能是前后径
            echo_code  # 回声水平  0: 无回声  2: 低回声  3: 等回声  4: 高回声
            border_clear  # 边界清晰模糊   0: 模糊  1: 清晰
            calcification[0] # 0:无微钙化  1: 有微钙化
            calcification[1] # 0:无粗钙化  1: 有粗钙化
            calcification[2] # 0:无环钙化  1: 有环钙化
            """

            # 3.0 diffuse, c_s

            try:
                anno, c_s = pymrcnn_part.ThyroidAIDoInference(
                    image_gray, height, width)
                print(anno, c_s)
            except:
                logging.error('part detection fail!')
                return thyroidrpc_pb2.ProtoResponse(
                    code=TIRADS_FAIL,
                    msg=ErrorDict[CLASSIFICATION_FAIL],
                    data=anypb)

            try:
                if nodule.w * nodule.h < 100000:
                    diffusion = diffuse.inference(image_gray, anno)
                else:
                    diffusion = 2
            except:
                logging.error('diffuse fail!')
                return thyroidrpc_pb2.ProtoResponse(
                    code=TIRADS_FAIL,
                    msg=ErrorDict[CLASSIFICATION_FAIL],
                    data=anypb)

            t1 = time.perf_counter()
            tirads = TiradsRecognition(image_gray, mask, is_debug=False)

            # 3.1 constitute
            constitute, cys_mask = tirads.find_or_report_constitute("report")

            # 3.2 comet
            if constitute <= 1:
                comet = 0
            else:
                comet = tirads.find_comet_calcify("report", cys_mask)

            # 3.3 ratio and hx_len, vx_len
            ar, vx, hx = tirads.estimate_aspect_ratio()

            hx_len, vx_len = tirads.get_axis_len(hx), tirads.get_axis_len(vx)

            pixels_one_cm = request.ppc

            hx_len /= pixels_one_cm
            vx_len /= pixels_one_cm
            hx_len = round(hx_len, 8)
            vx_len = round(vx_len, 8)

            if nodule.s == 1:
                hx_list = [hx_len, 0]
            else:
                hx_list = [0, hx_len]

            # 3.4 shape
            shape = tirads.classify_shape()

            # TODO: echo lever
            # 3.5 echo level
            #echo_code = tirads.compute_nodule_echo(mask_cyst=cys_mask)
            echo_code = 2

            # 3.6 border
            border_clear = tirads.border_clear_fuzzy()

            # 3.7 calcification 钙化
            if comet > 0:
                calcification = tirads.find_or_report_calcification(
                    pixels_one_cm)
            else:
                calcification = [0, 0, 0]
            # 加入弥漫性结果
            calcification.append(diffusion)

            list_tirads.append(
                thyroidrpc_pb2.OneTiradsRes(constitute=constitute,
                                            comet=comet,
                                            shape=shape,
                                            ratio=ar,
                                            hxlen=hx_list,
                                            vxlen=vx_len,
                                            echo_level=echo_code,
                                            border_clear=border_clear,
                                            calcification=calcification))
            # at last we set status ok
            list_ds.append(thyroidrpc_pb2.DetectionStatus(n=nodule.n,
                                                          status=0))
            t2 = time.perf_counter()
            logging.info('nodule {}, tirads time: {:.2f}'.format(
                nodule.n, (t2 - t1) * 1000))

            # save results to mongodb
            # if need update, replace old result
            if nodule.m > 0:
                old_index = nodule.m
                logging.info(f'rename nodule index: {nodule.m} -> {nodule.n}')
            else:
                old_index = nodule.n
                logging.info(f'and/replace nodule: {nodule.n}')

            try:
                userdb.collection.find_one_and_replace(
                    {
                        'uid': request.uid,
                        'scanID': request.scanID,
                        'imageID': request.imageID,
                        'nodule': old_index
                    },
                    {
                        'uid': request.uid,
                        'scanID': request.scanID,
                        'imageID': request.imageID,
                        'nodule': nodule.n,
                        'flag_report': False,  # means not used by report!
                        'pos': bbox_pos,
                        'pos_extend': nodule.pos,
                        'benign': benign,
                        'prob': prob,
                        'comet': comet,
                        'constitute': constitute,
                        'shape': shape,
                        'echo_level': echo_code,
                        'border_clear': border_clear,
                        'cut': nodule.s,
                        'ratio': ar,
                        'hxlen': hx_list,
                        'vxlen': vx_len,
                        'calcification': calcification
                    },
                    upsert=True  # if not find, just insert
                )
            except:
                logging.error('userdb.collection.find_one_and_replace fail!')
                return thyroidrpc_pb2.ProtoResponse(
                    code=DB_REPLACE_FAIL,
                    msg=ErrorDict[DB_REPLACE_FAIL],
                    data=anypb)

        # pack the proto result
        anypb.Pack(
            thyroidrpc_pb2.CTResponse(nums=len(request.nodule),
                                      ds=list_ds,
                                      nodule=list_nodule,
                                      bp=list_benign_prob,
                                      tirads=list_tirads,
                                      modify_flag=modify_flag))
        return thyroidrpc_pb2.ProtoResponse(code=0, msg='', data=anypb)