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)
    def DeleteNodule(self, request, context):
        """ delete nodules in one image
        """

        logging.info('---------------------------------------')
        logging.info(
            f'deleting nodules, uid: {request.uid}, scanID: {request.scanID}, imageID: {request.imageID}'
        )
        logging.info(f'total requst number: {len(request.noduleID)}')

        for nindex in request.noduleID:
            res = userdb.collection.delete_one({
                'uid': request.uid,
                'scanID': request.scanID,
                'imageID': request.imageID,
                'nodule': nindex
            })
            if res.deleted_count != 1:
                logging.error(f'deleting nodule fail at: {nindex}')
                return thyroidrpc_pb2.ProtoResponse(code=0, msg='', data=Any())
            else:
                logging.info(f'deleting nodule success: {nindex}')
                userdb.collection.update_many(
                    {
                        'uid': request.uid,
                        'scanID': request.scanID,
                        'nodule': nindex,
                        'flag_report': True
                    }, {'$set': {
                        'flag_report': False
                    }})

                userdb.report.delete_one({
                    'uid': request.uid,
                    'scanID': request.scanID,
                    'nodule': nindex
                })

        return thyroidrpc_pb2.ProtoResponse(code=0, msg='', data=Any())
    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 ModifyReport(self, request, context):
        """ modify report, only support modify one nodule per call!
        """
        """
        'benign': benign,
        'prob': prob,
        'shape': shape,
        'border_clear': border_clear,
        'constitute': constitute,
        'echo_level': echo_level,
        'cut': 0,  # just a placeholder, meaningless
        'ratio': ratio,
        'hxlen': hxlen,
        'vxlen': vxlen,
        'comet': comet,
        'calcification': calcification
        """

        logging.info('---------------------------------------')
        logging.info('modifing report')

        # 1. update benign
        if len(request.benign) == 1:
            newval = request.benign[0]
            userdb.report.find_one_and_update(
                {
                    'uid': request.uid,
                    'scanID': request.scanID,
                    'nodule': request.nindex
                }, {'$set': {
                    'flag_modified': True,
                    'benign': newval
                }})
        # 2. update prob
        if len(request.prob) == 1:
            newval = request.prob[0]
            userdb.report.find_one_and_update(
                {
                    'uid': request.uid,
                    'scanID': request.scanID,
                    'nodule': request.nindex
                }, {'$set': {
                    'flag_modified': True,
                    'prob': newval
                }})
        # 3. update constitute
        if len(request.constitute) == 1:
            newval = request.constitute[0]
            userdb.report.find_one_and_update(
                {
                    'uid': request.uid,
                    'scanID': request.scanID,
                    'nodule': request.nindex
                }, {'$set': {
                    'flag_modified': True,
                    'constitute': newval
                }})

        # 4. update comet
        if len(request.comet) == 1:
            newval = request.comet[0]
            userdb.report.find_one_and_update(
                {
                    'uid': request.uid,
                    'scanID': request.scanID,
                    'nodule': request.nindex
                }, {'$set': {
                    'flag_modified': True,
                    'comet': newval
                }})
        # 5. update shape
        if len(request.shape) == 1:
            newval = request.shape[0]
            userdb.report.find_one_and_update(
                {
                    'uid': request.uid,
                    'scanID': request.scanID,
                    'nodule': request.nindex
                }, {'$set': {
                    'flag_modified': True,
                    'shape': newval
                }})

        # 6. update echo_level
        if len(request.echo_level) == 1:
            newval = request.echo_level[0]
            doc = userdb.report.find_one({
                'uid': request.uid,
                'scanID': request.scanID,
                'nodule': request.nindex
            })
            logging.info('modify-before')
            logging.info(doc)

            userdb.report.find_one_and_update(
                {
                    'uid': request.uid,
                    'scanID': request.scanID,
                    'nodule': request.nindex
                }, {'$set': {
                    'flag_modified': True,
                    'echo_level': newval
                }})
            doc = userdb.report.find_one({
                'uid': request.uid,
                'scanID': request.scanID,
                'nodule': request.nindex
            })
            logging.info('modify-after')
            logging.info(doc)
        # 7. update border_clear
        if len(request.border_clear) == 1:
            newval = request.border_clear[0]
            userdb.report.find_one_and_update(
                {
                    'uid': request.uid,
                    'scanID': request.scanID,
                    'nodule': request.nindex
                }, {'$set': {
                    'flag_modified': True,
                    'border_clear': newval
                }})

        # 8. update calcification
        if len(request.calcification) == 3:
            newval = request.calcification
            userdb.report.find_one_and_update(
                {
                    'uid': request.uid,
                    'scanID': request.scanID,
                    'nodule': nindex
                }, {'$set': {
                    'flag_modified': True,
                    'calcification': newval
                }})

        return thyroidrpc_pb2.ProtoResponse(code=0, msg='', data=Any())
    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)