예제 #1
0
    def _finish_plot_depth_vs_tvu_qc(self):

        self.tvu_qc_ax.grid()
        self.tvu_qc_ax.set_xlim((round(self.tvu_qc_info.min / 0.1) * 0.1,
                                 self.tvu_qc_ax.get_xlim()[-1]))
        self.tvu_qc_ax.set_ylim(self.tvu_qc_ax.get_ylim()[::-1])
        self.tvu_qc_fig.text(.5,
                             .90,
                             'Grid source: %s, total nodes: %s' %
                             (self.tvu_qc_info.basename, '{:,}'.format(
                                 self.tvu_qc_info.nr_of_nodes)),
                             fontsize=12,
                             ha='center')
        self.tvu_qc_ax.set_ylabel('Depth')

        png_file = "%s.QAv5.depth_vs_tvu_qc.png" % os.path.splitext(
            self.grids.current_basename)[0]
        png_path = os.path.join(self.output_folder, png_file)
        png_path = Helper.truncate_too_long(png_path, left_truncation=True)

        self.tvu_qc_fig.text(.5,
                             .94,
                             'Node Depth vs. TVU QC',
                             fontsize=18,
                             ha='center')
        sub_title_txt = "Full TVU QC range"
        sub_title = self.tvu_qc_fig.text(.5,
                                         .86,
                                         sub_title_txt,
                                         fontsize=11,
                                         ha='center')

        self.tvu_qc_ax.set_xlabel(self.tvu_qc_info.histo_x_label)

        x_min, x_max = self.tvu_qc_ax.get_xlim()
        out_path = Helper.truncate_too_long(png_path.replace(
            '.png', '.full_range.png'),
                                            left_truncation=True)
        self.tvu_qc_fig.savefig(out_path, dpi=144, format='png')

        if x_max > 1.0:  # plot zoom on good data, if applicable

            self.tvu_qc_ax.set_xlim((x_min, 1.0))
            sub_title_txt = "Zoom on good data (TVU QC < 1.0)"
            sub_title.set_text(sub_title_txt)
            out_path = Helper.truncate_too_long(png_path.replace(
                '.png', '.zoom_on_good_data.png'),
                                                left_truncation=True)
            self.tvu_qc_fig.savefig(out_path, dpi=144, format='png')
예제 #2
0
    def write_tin(cls,
                  feature_list_a,
                  feature_list_b,
                  path,
                  list_of_list=True):
        if not os.path.exists(os.path.dirname(path)):
            raise RuntimeError("the passed path does not exist: %s" % path)

        path = Helper.truncate_too_long(path)

        if not isinstance(feature_list_a, list):
            raise RuntimeError(
                "the passed parameter as feature_list_a is not a list: %s" %
                type(feature_list_a))

        if not isinstance(feature_list_b, list):
            raise RuntimeError(
                "the passed parameter as feature_list_b is not a list: %s" %
                type(feature_list_b))

        s57 = S57()
        s57.create_tin_file(filename=path,
                            geo2edges_a=feature_list_a,
                            geo2edges_b=feature_list_b,
                            list_of_list=list_of_list)
예제 #3
0
    def _finish_plot_depth_vs_density(self):

        if self.density_info.nr_of_nodes > 1000:
            self.density_ax.set_xscale('log')
            self.density_ax.get_xaxis().set_major_formatter(ScalarFormatter())

        self.density_ax.grid()
        self.density_ax.set_xlim((round(self.density_info.min / 0.1) * 0.1,
                                  self.density_ax.get_xlim()[-1]))
        self.density_ax.set_ylim(self.density_ax.get_ylim()[::-1])
        self.density_fig.text(.5,
                              .90,
                              'Grid source: %s, total nodes: %s' %
                              (self.density_info.basename, '{:,}'.format(
                                  self.density_info.nr_of_nodes)),
                              fontsize=12,
                              ha='center')
        self.density_ax.set_ylabel('Depth')

        png_file = "%s.QAv5.depth_vs_density.png" % os.path.splitext(
            self.grids.current_basename)[0]
        png_path = os.path.join(self.output_folder, png_file)
        png_path = Helper.truncate_too_long(png_path, left_truncation=True)

        title = self.density_fig.text(.5,
                                      .94,
                                      'Node Depth vs. Sounding Density',
                                      fontsize=18,
                                      ha='center')
        self.density_ax.set_xlabel('Soundings per node')
        self.density_fig.savefig(png_path, dpi=144, format='png')
예제 #4
0
    def write(cls, feature_list, path):
        if not os.path.exists(os.path.dirname(path)):
            raise RuntimeError("the passed path does not exist: %s" % path)

        path = Helper.truncate_too_long(path)

        if not isinstance(feature_list, list):
            raise RuntimeError("the passed parameter as feature_list is not a list: %s" % type(feature_list))

        # generating header
        header = str()
        header += "[SVP_VERSION_2]\n"
        header += "%s\n" % path

        # generating body
        body = str()
        date_string = "%s" % datetime.now().strftime("%Y-%j %H:%M:%S")
        for m, ft in enumerate(feature_list):

            dd_lon = ft[0]
            dd_lat = ft[1]
            lon_d, lon_m, lon_s = Gd.dd2dms(dd_lon)
            lat_d, lat_m, lat_s = Gd.dd2dms(dd_lat)

            position_string = "{0:02d}:{1:02d}:{2:05.2f} {3:02d}:{4:02d}:{5:05.2f}".format(int(lat_d), int(lat_m),
                                                                                           lat_s, int(lon_d),
                                                                                           int(lon_m), lon_s)

            body += "Section " + date_string + " " + position_string + " Created by hyo2.qc\n"
            body += "    1.00    1500.00\n"
            body += "    15.00   1500.00\n"

        # open the file for writing
        with open(path, 'w') as fid:
            fid.write(header + body)
예제 #5
0
    def write_bluenotes(cls, feature_list, path, list_of_list=True):
        if not os.path.exists(os.path.dirname(path)):
            raise RuntimeError("the passed path does not exist: %s" % path)

        path = Helper.truncate_too_long(path)

        if not isinstance(feature_list, list):
            raise RuntimeError(
                "the passed parameter as feature_list is not a list: %s" %
                type(feature_list))

        if os.path.splitext(path)[-1] == '.shp':
            path = path[:-4]

        GdalAux()
        # create the data source
        try:
            ds = GdalAux.create_ogr_data_source(
                ogr_format=GdalAux.ogr_formats['ESRI Shapefile'],
                output_path=path)
            lyr = cls._create_ogr_point_lyr_and_fields(ds)

        except RuntimeError as e:
            logger.error("%s" % e)
            return

        if list_of_list:
            if len(feature_list[0]) != len(feature_list[1]):
                raise RuntimeError("invalid input for list of list")
            tmp_list = feature_list
            feature_list = list()
            for i, x in enumerate(tmp_list[0]):
                if len(tmp_list) >= 4:
                    feature_list.append(
                        [x, tmp_list[1][i], tmp_list[2][i], tmp_list[3][i]])
                else:
                    feature_list.append([x, tmp_list[1][i], tmp_list[2][i]])

        for feature in feature_list:
            ft = ogr.Feature(lyr.GetLayerDefn())
            ft.SetField('note', feature[2])
            if len(feature) >= 4:
                ft.SetField('info', feature[3])

            pt = ogr.Geometry(ogr.wkbPoint25D)
            pt.SetPoint(0, feature[0], feature[1])

            try:
                ft.SetGeometry(pt)

            except Exception as e:
                RuntimeError("%s > pt: %s, %s" % (e, feature[0], feature[1]))

            if lyr.CreateFeature(ft) != 0:
                raise RuntimeError("Unable to create feature")
            ft.Destroy()

        return True
예제 #6
0
    def write_bluenotes(cls, feature_list, path, list_of_list=True):
        if not os.path.exists(os.path.dirname(path)):
            raise RuntimeError("the passed path does not exist: %s" % path)

        path = Helper.truncate_too_long(path)

        if not isinstance(feature_list, list):
            raise RuntimeError(
                "the passed parameter as feature_list is not a list: %s" %
                type(feature_list))

        s57 = S57()
        s57.create_blue_notes_file(filename=path,
                                   geo2notes=feature_list,
                                   list_of_list=list_of_list)
예제 #7
0
    def write_soundings(cls, feature_list, path, list_of_list=False):
        """Feature list as list of long, lat, depth"""

        if not os.path.exists(os.path.dirname(path)):
            raise RuntimeError("the passed path does not exist: %s" % path)

        path = Helper.truncate_too_long(path)

        if not isinstance(feature_list, list):
            raise RuntimeError(
                "the passed parameter as feature_list is not a list: %s" %
                type(feature_list))

        s57 = S57()
        s57.create_soundings_file(filename=path,
                                  geo3s=feature_list,
                                  list_of_list=list_of_list)
예제 #8
0
    def write_soundings(cls, feature_list, path):
        """Feature list as list of long, lat, depth"""
        if not os.path.exists(os.path.dirname(path)):
            raise RuntimeError("the passed path does not exist: %s" % path)

        path = Helper.truncate_too_long(path)

        if not isinstance(feature_list, list):
            raise RuntimeError(
                "the passed parameter as feature_list is not a list: %s" %
                type(feature_list))

        if os.path.splitext(path)[-1] == '.shp':
            path = path[:-4]

        GdalAux()
        # create the data source
        try:
            ds = GdalAux.create_ogr_data_source(
                ogr_format=GdalAux.ogr_formats['ESRI Shapefile'],
                output_path=path)
            lyr = cls._create_ogr_point_lyr_and_fields(ds)

        except RuntimeError as e:
            logger.error("%s" % e)
            return

        for feature in feature_list:
            ft = ogr.Feature(lyr.GetLayerDefn())
            ft.SetField('info', "%.1f" % feature[2])

            pt = ogr.Geometry(ogr.wkbPoint25D)
            pt.SetPoint(0, feature[0], feature[1], feature[2])

            try:
                ft.SetGeometry(pt)

            except Exception as e:
                RuntimeError("%s > pt: %s, %s, %s" %
                             (e, feature[0], feature[1], feature[2]))

            if lyr.CreateFeature(ft) != 0:
                raise RuntimeError("Unable to create feature")
            ft.Destroy()

        return True
예제 #9
0
    def generate_pdf(self,
                     path: str,
                     title: str = "Document",
                     use_colors: bool = False,
                     small: bool = False) -> bool:
        """Generate a multiple-page document, with passed title and list of strings"""

        # this function heavily relies on PySide for pdf creation, the import is local
        # to the function so that the class can still be used also without Pyside

        try:
            from PySide2 import QtGui
            from PySide2 import QtCore
            from PySide2 import QtPrintSupport

        except (ImportError, ValueError, IOError):
            logger.warning(
                "PySide2 is not properly installed, thus you cannot create a pdf file"
            )
            self.display()
            return False

        # some preliminary tests
        if len(path) == 0:
            logger.warning("The passed file path is empty")
            return False
        path = os.path.abspath(path)
        if '.pdf' not in path.lower():
            logger.warning(
                "The passed file name has not the pdf extension: %s" % path)
            return False
        path = Helper.truncate_too_long(path)

        if len(self.records) == 0:
            logger.warning("The passed string list is empty")
            return False

        #
        # INITIAL SETTINGS
        #

        logger.debug("output: %s" % path)
        # delete the passed filename if it already exists
        if os.path.exists(path):
            os.remove(path)

        # prepare some drawing tools
        blue_pen = QtGui.QPen(QtGui.QColor(30, 30, 255))
        red_pen = QtGui.QPen(QtGui.QColor(255, 30, 30))
        green_pen = QtGui.QPen(QtGui.QColor(30, 200, 30))
        gray_pen = QtGui.QPen(QtGui.QColor(120, 120, 120))
        black_pen = QtGui.QPen(QtGui.QColor(30, 30, 30))
        if small:
            big_font = QtGui.QFont("Arial", 9)
            normal_font = QtGui.QFont("Arial", 6)
            bold_font = QtGui.QFont("Arial", 6, QtGui.QFont.Bold)
            small_font = QtGui.QFont("Arial", 5)
        else:
            big_font = QtGui.QFont("Arial", 10)
            normal_font = QtGui.QFont("Arial", 8)
            bold_font = QtGui.QFont("Arial", 8, QtGui.QFont.Bold)
            small_font = QtGui.QFont("Arial", 7)
        lc_flags = QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter | QtCore.Qt.TextWordWrap
        cc_flags = QtCore.Qt.AlignCenter | QtCore.Qt.TextWordWrap

        # create the printer for pdf
        printer = QtPrintSupport.QPrinter(
            QtPrintSupport.QPrinter.HighResolution)
        printer.setCreator("HydrOffice")
        printer.setDocName("HydrOffice.pdf")
        printer.setOutputFormat(QtPrintSupport.QPrinter.PdfFormat)
        printer.setPageSize(QtPrintSupport.QPrinter.A4)
        printer.setOrientation(QtPrintSupport.QPrinter.Portrait)
        printer.setOutputFileName(path)
        page_rect = printer.pageRect()
        doc_width = page_rect.width()
        doc_height = page_rect.height()
        doc_margin = page_rect.x()
        # print(printer.getPageMargins(QtGui.QPrinter.DevicePixel))
        # logger.info("Canvas: %sx%s" % (doc_width, doc_height))
        # logger.info("Margin: %s" % doc_margin)

        # check if everything is ok
        if not printer.isValid():
            logger.warning("the PDF printer is not valid")
            return False

        #
        # ACTUAL PRINTING
        #

        page_nr = 1  # a counter for pages
        section_nr = 1
        row_counter = 1
        if small:
            row_height = 330
        else:
            row_height = 220  # this represents the height of a single row of text
        max_rows = (doc_height - 2 * doc_margin) / row_height
        hor_pad = row_height / 2

        # logger.info("rows for page: %i" % max_rows)

        # here we start painting to the printer
        painter = QtGui.QPainter()
        if not painter.begin(printer):
            logger.warning("not painter begin on printer")
            return False

        def print_page_template():
            """Internal helper function that print borders, logo, time stamp, etc."""
            # external border
            painter.setPen(gray_pen)
            border_area = QtCore.QRect(doc_margin, doc_margin,
                                       doc_width - 2 * doc_margin,
                                       doc_height - 2 * doc_margin)
            painter.drawRect(border_area)

            # make top-area
            top_area = QtCore.QRect(doc_margin, doc_margin,
                                    doc_width - 2 * doc_margin, row_height * 2)
            painter.drawRect(top_area)
            # logo
            hyo_logo_path = os.path.join(AppInfo().app_media_path,
                                         'poweredby.png')
            if not os.path.exists(hyo_logo_path):
                raise RuntimeError("Unable to find logo: %s" % hyo_logo_path)
            hyo_logo = QtGui.QPixmap(hyo_logo_path)
            # print("logo size: %sx%s" % (hyo_logo.width(), hyo_logo.height()))
            logo_area = QtCore.QRect(
                doc_width / 2 - hyo_logo.width() / 2,
                doc_margin + (row_height * 2 - hyo_logo.height()) / 2,
                hyo_logo.width(), hyo_logo.height())
            painter.drawPixmap(logo_area, hyo_logo)

            # make bottom-area
            bottom_area = QtCore.QRect(doc_margin,
                                       doc_height - doc_margin - row_height,
                                       doc_width - 2 * doc_margin, row_height)
            painter.drawRect(bottom_area)
            # time-stamp
            now_time = datetime.datetime.now()
            painter.setFont(small_font)
            painter.drawText(
                bottom_area, cc_flags, "time-stamp: %s, %s v.%s" %
                (now_time.strftime("%a, %d %b %Y %H:%M:%S"), self.lib_name,
                 self.lib_version))
            # page number
            page_area = QtCore.QRect(doc_width - doc_margin - 2 * row_height,
                                     doc_height - doc_margin - row_height,
                                     2 * row_height, row_height)
            # painter.drawRect(page_area)
            painter.drawText(page_area, lc_flags, "Page %s" % page_nr)

            # set back to 'normal' font
            painter.setPen(black_pen)
            painter.setFont(normal_font)
            return True

        print_page_template()

        row_area = QtCore.QRect(doc_margin + hor_pad, doc_margin,
                                doc_width - 2 * doc_margin - 2 * hor_pad,
                                row_height)
        # painter.drawRect(row_area)

        first_page = True
        for content_item in self.records:

            # title document only for the first page
            if first_page:

                # document title
                if small:
                    row_counter = 4
                else:
                    row_counter = 3
                painter.setPen(blue_pen)
                painter.setFont(big_font)
                row_area.moveTo(row_area.x(),
                                row_area.y() + row_counter * row_height)
                painter.drawText(row_area, cc_flags, title)
                # painter.drawRect(row_area)
                # row_counter += 1

                # set back to 'normal' font
                painter.setPen(black_pen)
                painter.setFont(normal_font)

                first_page = False

            # manage a new page creation
            if row_counter >= (max_rows - 4):

                if not printer.newPage():
                    logger.warning(
                        "Failed in flushing page to disk, disk full?")
                    return False

                page_nr += 1
                print_page_template()
                row_area = QtCore.QRect(
                    doc_margin + hor_pad, doc_margin + row_height,
                    doc_width - 2 * doc_margin - 2 * hor_pad, row_height)
                row_counter = 1

            row_area.moveTo(row_area.x(), row_area.y() + row_height)
            # painter.drawRect(row_area)

            last_item = content_item.split(' ')[-1]
            if last_item.isdigit():

                if int(last_item) > 0:  # troubles -> red pen
                    if use_colors:
                        painter.setPen(red_pen)
                    painter.drawText(row_area, lc_flags, "- " + content_item)
                    if use_colors:
                        painter.setPen(black_pen)

                else:  # all good -> black pen
                    painter.drawText(row_area, lc_flags, "- " + content_item)

            elif last_item == "[SKIP_REP]":  # skip report for this item
                if use_colors:
                    painter.setPen(gray_pen)
                painter.drawText(row_area, lc_flags,
                                 "- " + content_item.rsplit(' ', 1)[0])
                if use_colors:
                    painter.setPen(black_pen)

            elif last_item == "[CHECK]" or last_item == "[TOTAL]":  # the string is a section separator
                # leave an empty row
                row_area.moveTo(row_area.x(), row_area.y() + row_height)
                row_counter += 1
                # write a numbered sections
                painter.setFont(bold_font)
                painter.drawText(row_area, lc_flags, "%s. %s" %
                                 (section_nr, content_item.rsplit(
                                     ' ', 1)[0]))  # cut the final token
                painter.setFont(normal_font)
                section_nr += 1

            elif last_item == "[SKIP_CHK]":  # the string is a section separator
                if use_colors:
                    painter.setPen(gray_pen)
                # leave an empty row
                row_area.moveTo(row_area.x(), row_area.y() + row_height)
                row_counter += 1
                # write a numbered sections
                painter.setFont(bold_font)
                painter.drawText(row_area, lc_flags, "%s. %s" %
                                 (section_nr, content_item.rsplit(
                                     ' ', 1)[0]))  # cut the final token
                painter.setFont(normal_font)
                section_nr += 1
                if use_colors:
                    painter.setPen(black_pen)

            elif last_item == "OK":  # no issues, green ok
                if use_colors:
                    painter.setPen(green_pen)
                painter.drawText(row_area, lc_flags, content_item)
                if use_colors:
                    painter.setPen(black_pen)

            else:
                painter.drawText(row_area, lc_flags, content_item)

            row_counter += 1
            # print("page %s, row %s" % (page_nr, row_counter))

        #
        # FINISHING THE PRINTING
        #
        painter.end()

        return True
예제 #10
0
    def write_tin(cls, feature_list_a, feature_list_b, path, list_of_list=True):
        if not os.path.exists(os.path.dirname(path)):
            raise RuntimeError("the passed path does not exist: %s" % path)

        path = Helper.truncate_too_long(path)

        if not isinstance(feature_list_a, list):
            raise RuntimeError("the passed parameter as feature_list_a is not a list: %s" % type(feature_list_a))

        if not isinstance(feature_list_b, list):
            raise RuntimeError("the passed parameter as feature_list_b is not a list: %s" % type(feature_list_b))

        if os.path.splitext(path)[-1] == '.kml':
            path = path[:-4]

        GdalAux()
        # create the data source
        try:
            ds = GdalAux.create_ogr_data_source(ogr_format=GdalAux.ogr_formats['KML'],
                                                output_path=path)
            lyr = cls._create_ogr_line_lyr_and_fields(ds)

        except RuntimeError as e:
            logger.error("%s" % e)
            return

        if list_of_list:
            if len(feature_list_a[0]) != len(feature_list_a[1]):
                raise RuntimeError("invalid input for list of list")
            if len(feature_list_b[0]) != len(feature_list_b[1]):
                raise RuntimeError("invalid input for list of list")
            if len(feature_list_a) != len(feature_list_b):
                raise RuntimeError("invalid input for list of list")

            tmp_list_a = feature_list_a
            feature_list_a = list()
            for i, x in enumerate(tmp_list_a[0]):
                feature_list_a.append([x, tmp_list_a[1][i]])

            tmp_list_b = feature_list_b
            feature_list_b = list()
            for i, x in enumerate(tmp_list_b[0]):
                feature_list_b.append([x, tmp_list_b[1][i]])

        for i, point in enumerate(feature_list_a):
            ft = ogr.Feature(lyr.GetLayerDefn())
            ft.SetField('note', "tin edge")

            ln = ogr.Geometry(ogr.wkbLineString)
            ln.AddPoint(point[0], point[1])
            ln.AddPoint(feature_list_b[i][0], feature_list_b[i][1])

            try:
                ft.SetGeometry(ln)

            except Exception as e:
                RuntimeError("%s > ln: %s, %s / %s, %s"
                             % (e, point[0], point[1], feature_list_b[i][0], feature_list_b[i][1]))

            if lyr.CreateFeature(ft) != 0:
                raise RuntimeError("Unable to create feature")
            ft.Destroy()

        return True
예제 #11
0
    def run(self):
        logger.info(
            "parameters for Grid QA: force-tvu-qc=%s, has_depth=%s, has_product_uncertainty=%s, "
            "has_density=%s, has_tvu_qc=%s" %
            (self.force_tvu_qc, self.has_depth, self.has_product_uncertainty,
             self.has_density, self.has_tvu_qc))

        logger.debug("modes -> objection detection: %s, full_coverage: %s" %
                     (self.objection_detection, self.full_coverage))

        if not self.has_depth:
            logger.critical("unable to identify the depth layer")
            return False

        success = True

        self.bathy_dict = defaultdict(int)
        self.density_dict = defaultdict(int)
        self.tvu_qc_dict = defaultdict(int)
        self.pct_od_dict = defaultdict(int)
        self.pct_cc_dict = defaultdict(int)

        self._init_infos()
        if self._depth_vs_density:
            self._init_plot_depth_vs_density()
        if self._depth_vs_tvu_qc:
            self._init_plot_depth_vs_tvu_qc()

        layers = list()
        if self.has_depth:
            layers.append(self.grids.depth_layer_name())
        if self.has_product_uncertainty:
            layers.append(self.grids.product_uncertainty_layer_name())
        if self.has_density:
            layers.append(self.grids.density_layer_name())
        if self.has_tvu_qc:
            layers.append(self.grids.tvu_qc_layer_name())
        logger.debug("selected layers: %s" % (layers, ))

        while self.grids.read_next_tile(layers=layers):

            if self.progress is not None:
                if self.progress.value < 50:
                    self.progress.add(quantum=10)
                elif self.progress.value < 75:
                    self.progress.add(quantum=1)
                elif self.progress.value < 90:
                    self.progress.add(quantum=0.1)
                elif self.progress.value <= 99:
                    self.progress.add(quantum=0.0001)

            # logger.debug("new tile")
            self._run_slice()
            self.grids.clear_tiles()

            # self._memory_info()

        # bathy
        self.bathy_dict = OrderedDict(
            sorted(self.bathy_dict.items(), key=lambda t: t[0]))
        bathy_counts = np.array(list(self.bathy_dict.values()))
        bathy_density = bathy_counts / bathy_counts.sum()
        bathy_cumsum = np.cumsum(bathy_density)
        bathy_bins = np.array(list(self.bathy_dict.keys())) / self.bathy_mul
        self.bathy_info.mode = bathy_bins[bathy_counts.argmax()]
        # noinspection PyTypeChecker
        self.bathy_info.p2_5 = bathy_bins[np.searchsorted(bathy_cumsum, 0.025)]
        # noinspection PyTypeChecker
        self.bathy_info.q1 = bathy_bins[np.searchsorted(bathy_cumsum, 0.25)]
        # noinspection PyTypeChecker
        self.bathy_info.median = bathy_bins[np.searchsorted(bathy_cumsum, 0.5)]
        # noinspection PyTypeChecker
        self.bathy_info.q3 = bathy_bins[np.searchsorted(bathy_cumsum, 0.75)]
        # noinspection PyTypeChecker
        self.bathy_info.p97_5 = bathy_bins[np.searchsorted(
            bathy_cumsum, 0.975)]
        # print("bathy: %s" % self.bathy_dict)
        # print("bathy: %s" % self.bathy_info)
        # save the histogram as png
        if self._hist_depth:
            bathy_png_file = "%s.QAv5.depth.png" % os.path.splitext(
                self.grids.current_basename)[0]
            bathy_png_path = os.path.join(self.output_folder, bathy_png_file)
            bathy_png_path = Helper.truncate_too_long(bathy_png_path,
                                                      left_truncation=True)
            GridQAV5.plot_hysto(layer_name="Depth",
                                bins=bathy_bins,
                                density=bathy_density,
                                bin_width=(1 / self.bathy_mul),
                                grid_info=self.bathy_info,
                                png_path=bathy_png_path)

        # density
        if self.has_density:
            self.density_dict = OrderedDict(
                sorted(self.density_dict.items(), key=lambda t: t[0]))
            # logger.debug("density dict: %s" % (self.density_dict, ))
            density_counts = np.array(list(self.density_dict.values()))
            density_density = density_counts / density_counts.sum()
            density_cumsum = np.cumsum(density_density)
            density_bins = np.array(list(
                self.density_dict.keys())) / self.density_mul

            if len(density_counts) > 0:

                self.density_info.mode = density_bins[density_counts.argmax()]
                # noinspection PyTypeChecker
                self.density_info.p2_5 = density_bins[np.searchsorted(
                    density_cumsum, 0.025)]
                # noinspection PyTypeChecker
                self.density_info.q1 = density_bins[np.searchsorted(
                    density_cumsum, 0.25)]
                # noinspection PyTypeChecker
                self.density_info.median = density_bins[np.searchsorted(
                    density_cumsum, 0.5)]
                # noinspection PyTypeChecker
                self.density_info.q3 = density_bins[np.searchsorted(
                    density_cumsum, 0.75)]
                # noinspection PyTypeChecker
                self.density_info.p97_5 = density_bins[np.searchsorted(
                    density_cumsum, 0.975)]
                self.density_info.pct_of_passed_nodes = self.density_info.nr_of_passed_nodes / \
                                                        float(self.density_info.nr_of_nodes)
                if self.density_info.pct_of_passed_nodes >= 0.95:
                    logger.debug(
                        "%.2f%% of grid nodes are populated with at least 5 soundings"
                        % (self.density_info.pct_of_passed_nodes * 100.0))
                else:
                    logger.warning(
                        "%.2f%% of grid nodes are populated with at least 5 soundings (it should be >= 95%%)"
                        % (self.density_info.pct_of_passed_nodes * 100.0))
                    success = False
                self.density_info.fail_left = 5
                # print("density: %s" % self.density_dict)
                # print("density: %s" % self.density_info)
                # save the histogram as png
                if self._hist_density:
                    density_png_file = "%s.QAv5.density.png" % os.path.splitext(
                        self.grids.current_basename)[0]
                    density_png_path = os.path.join(self.output_folder,
                                                    density_png_file)
                    density_png_path = Helper.truncate_too_long(
                        density_png_path, left_truncation=True)
                    GridQAV5.plot_hysto(layer_name="Density",
                                        bins=density_bins,
                                        density=density_density,
                                        bin_width=(1 / self.density_mul),
                                        grid_info=self.density_info,
                                        png_path=density_png_path)
                # save the depth vs. density plot as png
                if self._depth_vs_density:
                    self._finish_plot_depth_vs_density()
                # delete the density array
                self.density_values = None

        # tvu qc
        if (self.has_tvu_qc
                and not self.force_tvu_qc) or self.has_product_uncertainty:

            if len(self.tvu_qc_dict.values()) > 0:

                self.tvu_qc_dict = OrderedDict(
                    sorted(self.tvu_qc_dict.items(), key=lambda t: t[0]))
                tvu_qc_counts = np.array(list(self.tvu_qc_dict.values()))
                tvu_qc_density = tvu_qc_counts / tvu_qc_counts.sum()
                tvu_qc_cumsum = np.cumsum(tvu_qc_density)
                tvu_qc_bins = np.array(list(
                    self.tvu_qc_dict.keys())) / self.tvu_qc_mul
                self.tvu_qc_info.mode = tvu_qc_bins[tvu_qc_counts.argmax()]
                # noinspection PyTypeChecker
                self.tvu_qc_info.p2_5 = tvu_qc_bins[np.searchsorted(
                    tvu_qc_cumsum, 0.025)]
                # noinspection PyTypeChecker
                self.tvu_qc_info.q1 = tvu_qc_bins[np.searchsorted(
                    tvu_qc_cumsum, 0.25)]
                # noinspection PyTypeChecker
                self.tvu_qc_info.median = tvu_qc_bins[np.searchsorted(
                    tvu_qc_cumsum, 0.5)]
                # noinspection PyTypeChecker
                self.tvu_qc_info.q3 = tvu_qc_bins[np.searchsorted(
                    tvu_qc_cumsum, 0.75)]
                # noinspection PyTypeChecker
                self.tvu_qc_info.p97_5 = tvu_qc_bins[np.searchsorted(
                    tvu_qc_cumsum, 0.975)]
                self.tvu_qc_info.pct_of_passed_nodes = self.tvu_qc_info.nr_of_passed_nodes / float(
                    self.tvu_qc_info.nr_of_nodes)
                if self.tvu_qc_info.pct_of_passed_nodes >= 0.95:
                    logger.debug(
                        "%.2f%% of grid nodes meets the maximum allowable TVU"
                        % (self.tvu_qc_info.pct_of_passed_nodes * 100.0))
                else:
                    logger.warning(
                        "%.2f%% of grid nodes meets the maximum allowable TVU (it should be >= 95%%)"
                        % (self.tvu_qc_info.pct_of_passed_nodes * 100.0))
                    success = False
                self.tvu_qc_info.fail_right = 1
                # print("tvu qc: %s" % self.tvu_qc_dict)
                # print("tvu qc: %s" % self.tvu_qc_info)
                # save the histogram as png
                if self._hist_tvu_qc:
                    tvu_qc_png_file = "%s.QAv5.tvu_qc.png" % os.path.splitext(
                        self.grids.current_basename)[0]
                    tvu_qc_png_path = os.path.join(self.output_folder,
                                                   tvu_qc_png_file)
                    tvu_qc_png_path = Helper.truncate_too_long(
                        tvu_qc_png_path, left_truncation=True)
                    GridQAV5.plot_hysto(layer_name="TVU QC",
                                        bins=tvu_qc_bins,
                                        density=tvu_qc_density,
                                        bin_width=(1 / self.tvu_qc_mul),
                                        grid_info=self.tvu_qc_info,
                                        png_path=tvu_qc_png_path)
                # save the depth vs. tvu qc plot as png
                if self._depth_vs_tvu_qc:
                    self._finish_plot_depth_vs_tvu_qc()
                # delete the array
                self.tvu_qc_values = None
                # del self.grids.tvu_qc

        # res pct
        if self.grids.is_vr():

            # - PCT OD
            if self.objection_detection:
                self.pct_od_dict = OrderedDict(
                    sorted(self.pct_od_dict.items(), key=lambda t: t[0]))
                # logger.debug("%s" % dict(self.pct_od_dict))
                pct_od_counts = np.array(list(self.pct_od_dict.values()))
                pct_od_density = pct_od_counts / pct_od_counts.sum()
                pct_od_cumsum = np.cumsum(pct_od_density)
                pct_od_bins = np.array(list(
                    self.pct_od_dict.keys())) / self.pct_od_mul
                self.pct_od_info.mode = pct_od_bins[pct_od_counts.argmax()]
                # noinspection PyTypeChecker
                self.pct_od_info.p2_5 = pct_od_bins[np.searchsorted(
                    pct_od_cumsum, 0.025)]
                # noinspection PyTypeChecker
                self.pct_od_info.q1 = pct_od_bins[np.searchsorted(
                    pct_od_cumsum, 0.25)]
                # noinspection PyTypeChecker
                self.pct_od_info.median = pct_od_bins[np.searchsorted(
                    pct_od_cumsum, 0.5)]
                # noinspection PyTypeChecker
                self.pct_od_info.q3 = pct_od_bins[np.searchsorted(
                    pct_od_cumsum, 0.75)]
                # noinspection PyTypeChecker
                self.pct_od_info.p97_5 = pct_od_bins[np.searchsorted(
                    pct_od_cumsum, 0.975)]
                self.pct_od_info.pct_of_passed_nodes = self.pct_od_info.nr_of_passed_nodes / float(
                    self.pct_od_info.nr_of_nodes)
                if self.pct_od_info.pct_of_passed_nodes >= 0.95:
                    logger.debug(
                        "%.2f%% of grid nodes meets the coarsest allowable resolution"
                        % (self.pct_od_info.pct_of_passed_nodes * 100.0))
                else:
                    logger.warning(
                        "%.2f%% of grid nodes meets the coarsest allowable resolution (it should be >= 95%%)"
                        % (self.pct_od_info.pct_of_passed_nodes * 100.0))
                    success = False
                self.pct_od_info.fail_right = 1
                # print("pct od: %s" % self.pct_od_dict)
                # print("pct od: %s" % self.pct_od_info)
                # save the histogram as png
                if self._hist_pct_res:
                    pct_od_png_file = "%s.QAv5.pct_res.obj_det.png" % os.path.splitext(
                        self.grids.current_basename)[0]
                    pct_od_png_path = os.path.join(self.output_folder,
                                                   pct_od_png_file)
                    pct_od_png_path = Helper.truncate_too_long(
                        pct_od_png_path, left_truncation=True)
                    GridQAV5.plot_hysto(layer_name="RES OD",
                                        bins=pct_od_bins,
                                        density=pct_od_density,
                                        bin_width=0.1,
                                        grid_info=self.pct_od_info,
                                        png_path=pct_od_png_path)
                # delete the array
                self.pct_od_values = None

            # - PCT CC
            if self.full_coverage:
                self.pct_cc_dict = OrderedDict(
                    sorted(self.pct_cc_dict.items(), key=lambda t: t[0]))
                # logger.debug("%s" % dict(self.pct_cc_dict))
                pct_cc_counts = np.array(list(self.pct_cc_dict.values()))
                pct_cc_density = pct_cc_counts / pct_cc_counts.sum()
                pct_cc_cumsum = np.cumsum(pct_cc_density)
                pct_cc_bins = np.array(list(
                    self.pct_cc_dict.keys())) / self.pct_cc_mul
                self.pct_cc_info.mode = pct_cc_bins[pct_cc_counts.argmax()]
                # noinspection PyTypeChecker
                self.pct_cc_info.p2_5 = pct_cc_bins[np.searchsorted(
                    pct_cc_cumsum, 0.025)]
                # noinspection PyTypeChecker
                self.pct_cc_info.q1 = pct_cc_bins[np.searchsorted(
                    pct_cc_cumsum, 0.25)]
                # noinspection PyTypeChecker
                self.pct_cc_info.median = pct_cc_bins[np.searchsorted(
                    pct_cc_cumsum, 0.5)]
                # noinspection PyTypeChecker
                self.pct_cc_info.q3 = pct_cc_bins[np.searchsorted(
                    pct_cc_cumsum, 0.75)]
                # noinspection PyTypeChecker
                self.pct_cc_info.p97_5 = pct_cc_bins[np.searchsorted(
                    pct_cc_cumsum, 0.975)]
                self.pct_cc_info.pct_of_passed_nodes = self.pct_cc_info.nr_of_passed_nodes / float(
                    self.pct_cc_info.nr_of_nodes)
                if self.pct_cc_info.pct_of_passed_nodes >= 0.95:
                    logger.debug(
                        "%.2f%% of grid nodes meets the coarsest allowable resolution"
                        % (self.pct_cc_info.pct_of_passed_nodes * 100.0))
                else:
                    logger.warning(
                        "%.2f%% of grid nodes meets the coarsest allowable resolution (it should be >= 95%%)"
                        % (self.pct_cc_info.pct_of_passed_nodes * 100.0))
                    success = False
                self.pct_cc_info.fail_right = 1
                # print("pct od: %s" % self.pct_cc_dict)
                # print("pct od: %s" % self.pct_cc_info)
                # save the histogram as png
                if self._hist_pct_res:
                    pct_cc_png_file = "%s.QAv5.pct_res.full_cov.png" % os.path.splitext(
                        self.grids.current_basename)[0]
                    pct_cc_png_path = os.path.join(self.output_folder,
                                                   pct_cc_png_file)
                    pct_cc_png_path = Helper.truncate_too_long(
                        pct_cc_png_path, left_truncation=True)
                    GridQAV5.plot_hysto(layer_name="RES FC",
                                        bins=pct_cc_bins,
                                        density=pct_cc_density,
                                        bin_width=0.1,
                                        grid_info=self.pct_cc_info,
                                        png_path=pct_cc_png_path)
                # delete the array
                self.pct_cc_values = None

        return success