【数据可视化高手必备】:subplot_adjust中left、right、top、bottom的精确计算方法

第一章:subplot_adjust 间距的基本概念

在使用 Matplotlib 进行多子图可视化时,合理控制子图之间的空白区域对图表的可读性和美观性至关重要。`subplot_adjust` 是 Matplotlib 提供的一个核心方法,用于手动调节子图布局中的边距和间距,避免标题、标签或刻度之间发生重叠。

功能与参数说明

`plt.subplots_adjust()` 允许用户通过设置以下关键参数来微调布局:
  • left, right, top, bottom:定义子图区域距离画布左侧、右侧、顶部和底部的归一化坐标(范围通常为 0~1)
  • wspace:控制相邻子图之间的水平间距(以子图宽度的倍数表示)
  • hspace:控制相邻子图之间的垂直间距(以子图高度的倍数表示)

基础使用示例

# 创建包含两个子图的布局,并调整间距
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))

ax1.plot([1, 2, 3], [1, 4, 2])
ax1.set_title("Plot 1")
ax2.plot([1, 2, 3], [3, 1, 5])
ax2.set_title("Plot 2")

# 调整子图间的水平间距和边距
plt.subplots_adjust(left=0.1, right=0.9, wspace=0.4, hspace=0.5)
plt.show()
上述代码中,`wspace=0.4` 表示子图间水平间距为子图宽度的 40%,而 `left` 和 `right` 确保图形不紧贴画布边缘。这种细粒度控制特别适用于包含长标签或多个子图的复杂布局。

常用参数参考表

参数默认值说明
left0.125左边界留白比例
bottom0.1下边界留白比例
wspace0.2子图间水平间距(相对宽度)
hspace0.2子图间垂直间距(相对高度)

第二章:理解 left、right、top、bottom 参数的理论基础

2.1 Matplotlib 坐标系与子图布局原理

Matplotlib 的绘图基础建立在坐标系与子图布局机制之上。图形窗口中的每个元素都依赖于特定的坐标系统进行定位,主要包括数据坐标、轴坐标和显示坐标。
坐标系类型
  • 数据坐标:默认坐标系,与数据范围对齐;
  • 轴坐标:归一化坐标,范围 [0, 1],适用于相对定位;
  • 显示坐标:以像素为单位,用于底层渲染控制。
子图布局管理
使用 plt.subplots() 可创建规则网格布局:
fig, axes = plt.subplots(2, 3, figsize=(9, 6))
for i, ax in enumerate(axes.flat):
    ax.plot([0, 1], [0, i])
    ax.set_title(f'Subplot {i+1}')
上述代码生成 2×3 子图网格,axes.flat 提供扁平化访问方式。figsize 控制整体画布尺寸,避免子图重叠。
自动布局优化
参数作用
tight_layout=True自动调整子图间距
constrained_layout=True实时优化布局适应窗口

2.2 left 与 right 参数的边界定义及影响范围

在区间操作中,`left` 与 `right` 参数通常用于界定数据处理的起始与结束位置。其边界定义直接影响算法的执行效率与结果准确性。
边界取值策略
  • 闭区间 [left, right]:包含两端点,适用于分治算法如二分查找;
  • 左闭右开 [left, right):常见于循环遍历,避免越界访问。
代码实现示例
func binarySearch(arr []int, target int) int {
    left, right := 0, len(arr)-1
    for left <= right {
        mid := left + (right-left)/2
        if arr[mid] == target {
            return mid
        } else if arr[mid] < target {
            left = mid + 1
        } else {
            right = mid - 1
        }
    }
    return -1
}
上述代码中,`left` 和 `right` 初始化为数组首尾索引,循环条件依赖于闭区间判断(`left <= right`),确保搜索空间正确收缩。若将 `right` 初始化为 `len(arr)` 并采用 `left < right` 判断,则需调整中点更新逻辑,体现边界选择对控制流的深层影响。

2.3 top 与 bottom 参数在垂直空间分配中的作用

在布局系统中,topbottom 参数用于控制元素在垂直方向上的空间分配与定位。它们常用于确定组件距离容器顶部或底部的偏移量。
参数行为解析
  • top:指定元素上边缘与父容器上边缘的距离
  • bottom:指定元素下边缘与父容器下边缘的距离
当同时设置时,元素高度将由两者共同决定,公式为:
height = parent_height - top - bottom
代码示例
.container {
  position: absolute;
  top: 20px;
  bottom: 30px;
  left: 0;
  right: 0;
}
上述样式使容器在垂直方向保留 20px 的顶部边距和 30px 的底部边距,内容区域高度自动填充剩余空间。此机制广泛应用于自适应布局中,确保界面元素在不同屏幕尺寸下合理分布。

2.4 四个参数之间的相互制约关系分析

在系统调优过程中,延迟(Latency)、吞吐量(Throughput)、资源消耗(Resource Usage)和一致性(Consistency)四个核心参数存在显著的相互制约。
权衡三角:性能与一致性的博弈
提升一致性通常需要增加同步开销,导致延迟上升、吞吐量下降。例如,在分布式数据库中启用强一致性模式:

replicaWriteMode = "synchronous"  // 强同步写入
quorum = (n/2 + 1)                // 法定数量确认
该配置虽保障数据安全,但每次写操作需等待多数节点响应,显著增加平均延迟。
资源与吞吐的正向关联
提高吞吐量往往依赖更多CPU、内存等资源投入。以下为典型资源配置表:
并发线程数CPU占用率每秒处理请求数
1035%1,200
5078%4,500
10096%5,100
可见,吞吐增长伴随资源使用趋近饱和,存在边际递减效应。

2.5 默认值与自适应布局的冲突场景解析

在响应式设计中,组件默认值可能与自适应布局规则产生冲突。当预设尺寸、间距或字体大小未适配不同视口时,会导致布局错位或内容溢出。
典型冲突示例

.container {
  width: 300px; /* 固定宽度破坏响应性 */
  margin: auto;
}
@media (max-width: 480px) {
  .container {
    width: 100%;
  }
}
上述代码中,.container 的固定宽度在移动端需通过媒体查询强制覆盖,若遗漏则导致横向滚动。默认值优先级高于自适应规则时,易引发渲染异常。
常见冲突类型归纳
  • 固定像素尺寸阻碍弹性布局
  • 默认外边距在栅格系统中造成断行
  • 字体大小未使用相对单位影响可访问性

第三章:实际绘图中常见间距问题与解决方案

3.1 标签被截断:如何通过调整 bottom 和 left 挽救

在图表或可视化组件中,标签常因容器空间不足而被截断。最常见的表现是底部(bottom)或左侧(left)的文本显示不全。
问题成因
CSS 的默认布局可能压缩 SVG 或 Canvas 元素的边距,导致坐标轴标签溢出可视区域。
解决方案
通过调整 CSS 中的 bottomleft 属性,为标签预留足够空间:

.chart-container {
  position: relative;
  padding-bottom: 40px; /* 为底部标签留白 */
}

.axis-label {
  position: absolute;
  bottom: 10px; /* 调整底部偏移 */
  left: 50px;   /* 调整左侧起始位置 */
}
上述代码通过 position: absolute 精确控制标签位置,bottom 防止与图表重叠,left 避免与纵轴冲突。结合 padding-bottom 确保容器不裁剪内容,实现完整显示。

3.2 多子图重叠:利用 right 和 top 优化横向与纵向间隔

在 Matplotlib 中绘制多子图时,合理使用 subplots_adjustrighttop 参数可有效控制画布边距,避免子图重叠。
参数作用解析
  • right:控制子图区域右边界位置(0~1),值越小右侧留白越多;
  • top:调整子图上边界,防止标题或图例被截断。
代码示例
plt.subplots_adjust(right=0.9, top=0.9)
该设置将子图区域限制在画布的 90% 范围内,为图例和标题预留空间。结合 leftbottom 可实现四周边距均衡,提升可视化布局美观性。

3.3 高分辨率输出下的间距失真修复技巧

在高DPI显示环境下,UI元素常因像素对齐问题出现间距失真。核心解决方案是启用设备像素比感知布局。
使用CSS处理像素对齐
@media (-webkit-min-device-pixel-ratio: 2) {
  .container {
    margin: calc(10px * (1 / devicePixelRatio));
    transform: translateZ(0);
  }
}
上述代码根据设备像素比动态调整外边距,避免亚像素渲染导致的错位。devicePixelRatio 获取物理像素与CSS像素的比率,确保布局在Retina屏等高密度屏幕上保持清晰。
JavaScript动态校正策略
  • 监听window.devicePixelRatio变化事件
  • 重绘前调用ctx.resetTransform()重置画布变换
  • 使用整数坐标绘制避免模糊

第四章:精确计算间距的实用方法与案例演练

4.1 基于字体大小和刻度数量的动态 left/right 计算模型

在响应式图表布局中,标签位置的精准控制至关重要。为实现坐标轴标签的自动对齐,提出一种基于字体大小与刻度数量的动态计算模型。
核心计算逻辑
该模型通过分析当前字体尺寸(font-size)与刻度总数(tickCount),动态调整标签的 leftright 偏移值,避免文本重叠。

function calculateLabelOffset(fontSize, tickCount, containerWidth) {
  const baseOffset = fontSize * 1.5;
  const dynamicFactor = containerWidth / (tickCount + 1);
  return {
    left: baseOffset,
    right: baseOffset + (containerWidth - dynamicFactor)
  };
}
上述代码中,baseOffset 确保最小边距,dynamicFactor 根据容器宽度与刻度密度调整间距,提升可读性。
参数影响分析
  • fontSize:直接影响标签占用空间,决定基础偏移量;
  • tickCount:刻度越多,单个标签可用空间越小,需压缩间隔;
  • containerWidth:布局宽度越大,可分配的弹性空间越多。

4.2 结合 tight_layout() 与手动 subplot_adjust 的协同策略

在复杂图表布局中,tight_layout() 能自动优化子图间距,但对特殊排版需求可能不足。此时可先调用 tight_layout() 进行初步调整,再使用 subplots_adjust() 微调特定参数。
协同使用流程
  • 优先启用 tight_layout=True 实现自适应布局
  • 通过 plt.subplots_adjust() 覆盖局部参数,如底部边距或列间距
  • 避免两者冲突:设置 tight_layout=False 后再手动调整
# 先自动布局,再微调
fig, axes = plt.subplots(2, 2, figsize=(8, 6), tight_layout=True)
fig.subplots_adjust(bottom=0.1, wspace=0.3)  # 覆盖列间距和底边
上述代码中,tight_layout() 初步防止重叠,subplots_adjust() 进一步控制 wspace(列间距)和 bottom(底部留白),实现精细排版。

4.3 自动化脚本:根据画布尺寸反推最优 bottom/top 值

在复杂UI布局中,固定定位元素的 `bottom` 或 `top` 值常需适配不同分辨率画布。手动设置易出错且维护成本高,因此引入自动化脚本动态计算最优值。
核心计算逻辑
脚本通过获取画布高度与组件尺寸,反推出不重叠、不溢出的定位值:
function calculateOptimalPosition(canvasHeight, elementHeight, safeMargin = 20) {
  // 计算底部安全区域起始位置
  const maxBottom = canvasHeight - elementHeight - safeMargin;
  return {
    top: safeMargin,
    bottom: Math.max(maxBottom, safeMargin)
  };
}
上述函数接收画布总高、元素高度及安全边距,返回两个候选值。`Math.max` 确保在小屏环境下仍保留最小可视空间。
批量处理策略
  • 遍历所有固定定位元素
  • 读取其渲染后高度
  • 调用计算函数注入CSS变量

4.4 复杂网格布局中的多子图统一间距控制实践

在复杂网格布局中,多个子图的对齐与间距一致性直接影响可视化效果的专业性。为实现统一控制,推荐使用 Matplotlib 的 `GridSpec` 配合 `subplots_adjust` 方法进行精细化管理。
参数化间距控制策略
通过设置 `wspace` 和 `hspace` 参数,可分别调节子图之间的水平和垂直间距:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec

fig = plt.figure(figsize=(10, 6))
gs = GridSpec(2, 3, figure=fig, wspace=0.3, hspace=0.4)

ax1 = fig.add_subplot(gs[0, :2])
ax2 = fig.add_subplot(gs[0, 2])
ax3 = fig.add_subplot(gs[1, :])
上述代码中,`wspace=0.3` 表示列间空白占子图宽度的30%,`hspace=0.4` 控制行间距。数值经归一化处理,避免因尺寸变化导致布局错乱。
响应式布局建议
  • 优先使用相对单位而非固定像素值
  • 结合 constrained_layout=True 自动优化位置
  • 在子图内容动态变化时重绘前调用 plt.tight_layout()

第五章:总结与高级应用建议

性能调优实战策略
在高并发场景下,数据库连接池的配置直接影响系统吞吐量。以下是一个基于 Go 语言的连接池优化配置示例:

db.SetMaxOpenConns(100)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(time.Hour)
该配置通过限制最大打开连接数防止资源耗尽,同时设置合理的空闲连接和生命周期,避免长时间空闲连接占用数据库资源。
微服务架构中的熔断机制
为提升系统稳定性,建议在服务间调用中集成熔断器模式。Hystrix 或 Sentinel 可有效防止雪崩效应。以下是关键参数配置参考:
参数推荐值说明
请求阈值20触发熔断的最小请求数
错误率阈值50%错误率超过此值进入熔断状态
熔断时长30s熔断后等待恢复的时间
日志与监控集成建议
生产环境应统一日志格式并接入集中式监控平台。推荐使用如下结构化日志字段:
  • timestamp: ISO8601 时间戳
  • level: 日志级别(error, warn, info)
  • service_name: 微服务名称
  • trace_id: 分布式追踪ID
  • message: 可读性日志内容
结合 Prometheus 抓取指标,Grafana 展示仪表盘,可实现毫秒级故障定位响应。
import cv2 import os import numpy as np import matplotlib.pyplot as plt import skimage.io as io from collections import Counter from PIL import Image import pandas as pd from scipy.ndimage import gaussian_filter1d from sklearn.cluster import KMeans from scipy.signal import find_peaks from sklearn.preprocessing import StandardScaler plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 # 1. 读取图像并进行预处理 data_dir = './附件4' path = data_dir + '/*.bmp' coll = io.ImageCollection(path) # 读入灰度图像 img_num = len(coll) # 转矩阵并二值化 img = np.asarray(coll) for i in range(len(coll)): img[i] = cv2.adaptiveThreshold( src=img[i], maxValue=1, adaptiveMethod=cv2.ADAPTIVE_THRESH_MEAN_C, thresholdType=cv2.THRESH_BINARY, blockSize=13, C=2 ) print("图像数据形状:", img.shape) # 2. 计算每张图片的左右边距 left = [] right = [] for i in range(img.shape[0]): # 计算左边距 count = 0 for y in range(img.shape[2]): # 列 if np.any(img[i, :, y] == 0): # 该列有文字 break count += 1 left.append(count) # 计算右边距 count = 0 for y in range(img.shape[2] - 1, -1, -1): # 从右向左 if np.any(img[i, :, y] == 0): # 该列有文字 break count += 1 right.append(count) plt.figure(figsize=(10, 6)) plt.scatter(range(len(left)), left, label='左边距') plt.scatter(range(len(right)), right, label='右边距') plt.title('碎片左右边距分布') plt.xlabel('碎片索引') plt.ylabel('边距大小(像素)') plt.legend() plt.grid(True) plt.show() print("左边距统计:", Counter(left)) print("右边距统计:", Counter(right)) # 3. 确定行首和行尾碎片 fenge = 10 # 边距阈值,超过此值认为是行首或行尾 col = 19 # 列数 row = 11 # 行数 # 找出行尾碎片 end_index = [i for i, r in enumerate(right) if r >= fenge] print(f"行尾碎片数量: {len(end_index)}") # 找出行首碎片 first_index = [i for i, l in enumerate(left) if l >= fenge] print(f"行首碎片数量: {len(first_index)}") # 4. 提取四线三行特征 def extract_english_features(image): """ 提取英文文本的四线三行特征 image: 预处理后的二值图像 (0=文字, 1=空白) """ h, w = image.shape features = [] # 1. 计算水平投影 horizontal_proj = np.sum(1 - image, axis=1) # 反转:文字区域值高 # 2. 平滑投影曲线 smoothed_proj = gaussian_filter1d(horizontal_proj, sigma=1.5) # 3. 检测文本行区域 line_regions = [] in_text = False start = 0 threshold = 0.1 * np.max(smoothed_proj) for i, val in enumerate(smoothed_proj): if val > threshold and not in_text: in_text = True start = i elif val <= threshold and in_text: in_text = False line_regions.append((start, i)) # 处理最后一行 if in_text: line_regions.append((start, h - 1)) # 4. 分析每行的四线三行特征 line_features = [] for start, end in line_regions: line_height = end - start if line_height < 5: # 忽略太小的区域 continue line_img = image[start:end, :] # 计算垂直投影 vertical_proj = np.sum(1 - line_img, axis=0) # 检测基线(字母底部) baseline_pos = np.argmax(vertical_proj) # 检测中线(小写字母高度) mid_threshold = 0.5 * np.max(vertical_proj) mid_region = np.where(vertical_proj > mid_threshold)[0] if len(mid_region) > 0: midline_pos = np.mean(mid_region) else: midline_pos = baseline_pos - 0.3 * line_height # 检测顶线(大写字母高度) top_threshold = 0.7 * np.max(vertical_proj) top_region = np.where(vertical_proj > top_threshold)[0] if len(top_region) > 0: topline_pos = np.mean(top_region) else: topline_pos = midline_pos - 0.2 * line_height # 检测底线(下伸字母底部) bottom_threshold = 0.6 * np.max(vertical_proj) bottom_region = np.where(vertical_proj > bottom_threshold)[0] if len(bottom_region) > 0: bottomline_pos = np.mean(bottom_region) else: bottomline_pos = baseline_pos + 0.2 * line_height line_features.append({ 'start': start, 'end': end, 'baseline': baseline_pos, 'midline': midline_pos, 'topline': topline_pos, 'bottomline': bottomline_pos, 'height': line_height }) return line_features # 提取所有碎片的四线三行特征 fragment_features = [] for i in range(img.shape[0]): features = extract_english_features(img[i]) fragment_features.append(features) # 可视化特征提取结果 def visualize_fragment_features(fragment_id): fragment = img[fragment_id] plt.figure(figsize=(10, 4)) plt.imshow(fragment, cmap='gray') features = fragment_features[fragment_id] for line in features: plt.axhline(y=line['start'], color='r', linestyle='-', alpha=0.7) plt.axhline(y=line['end'], color='r', linestyle='-', alpha=0.7) plt.axhline(y=line['baseline'], color='b', linestyle='--', alpha=0.7) plt.axhline(y=line['midline'], color='g', linestyle='--', alpha=0.7) plt.axhline(y=line['topline'], color='m', linestyle='--', alpha=0.7) plt.axhline(y=line['bottomline'], color='c', linestyle='--', alpha=0.7) plt.title(f'碎片 {fragment_id} - 四线三行特征') plt.axis('off') plt.show() # 随机选择几个碎片可视化 print("随机可视化5个碎片的四线三行特征...") for i in np.random.choice(len(img), min(5, len(img))): visualize_fragment_features(i) # 5. 确定整个文档的行结构 all_baselines = [] all_heights = [] for features in fragment_features: for line in features: all_baselines.append(line['baseline']) all_heights.append(line['height']) # 聚类确定文档的行基线位置 if len(all_baselines) > 0: kmeans = KMeans(n_clusters=row, random_state=42) baseline_clusters = kmeans.fit_predict(np.array(all_baselines).reshape(-1, 1)) # 获取每个聚类的中心点(即文档的行基线位置) document_baselines = sorted([center[0] for center in kmeans.cluster_centers_]) print("文档行基线位置:", document_baselines) else: # 如果没有检测到基线,使用默认值 document_baselines = np.linspace(20, 150, row).tolist() print("警告: 未检测到基线,使用默认基线位置:", document_baselines) # 6. 将碎片分配到行 row_assignments = [[] for _ in range(row)] # 11行 for frag_id, features in enumerate(fragment_features): if not features: # 没有检测到文本行 # 尝试根据空白区域分配 if left[frag_id] >= fenge: row_assignments[0].append(frag_id) # 可能是第一行 elif right[frag_id] >= fenge: row_assignments[-1].append(frag_id) # 可能是最后一行 else: # 无法确定,暂不分配 pass continue # 计算碎片与每行的匹配分数 match_scores = [] for doc_baseline in document_baselines: # 找到碎片中最接近该基线的行 min_dist = float('inf') for line in features: dist = abs(line['baseline'] - doc_baseline) if dist < min_dist: min_dist = dist match_scores.append(min_dist) # 选择最匹配的行(距离最小的) best_row = np.argmin(match_scores) row_assignments[best_row].append(frag_id) # 处理未分配的碎片 unassigned = [] for frag_id in range(len(img)): assigned = False for row in row_assignments: if frag_id in row: assigned = True break if not assigned: unassigned.append(frag_id) print(f"未分配的碎片数量: {len(unassigned)}") # 7. 可视化行分组结果 def visualize_row_grouping(row_assignments): for row_id, fragments in enumerate(row_assignments): if not fragments: print(f"行 {row_id} 没有碎片") continue print(f"行 {row_id} 有 {len(fragments)} 个碎片") # 创建行预览 row_img = img[fragments[0]] for frag_id in fragments[1:]: row_img = np.hstack((row_img, img[frag_id])) plt.figure(figsize=(15, 3)) plt.imshow(row_img, cmap='gray') plt.title(f'行 {row_id} 分组预览') plt.axis('off') plt.show() print("行分组结果预览...") visualize_row_grouping(row_assignments) # 8. 人工干预:调整行分组 print("当前行分配:") for row_id, fragments in enumerate(row_assignments): print(f"行 {row_id}: {fragments}") # 模拟人工干预 adjustments = input("输入需要调整的碎片ID和目标行(格式: 碎片ID:目标行, 多个用分号分隔): ") if adjustments: for adj in adjustments.split(';'): if ':' in adj: frag_id, target_row = map(int, adj.split(':')) # 从原行中移除 for row in row_assignments: if frag_id in row: row.remove(frag_id) break # 添加到目标行 if 0 <= target_row < len(row_assignments): row_assignments[target_row].append(frag_id) # 9. 行内排序 def sort_fragments_in_row(fragments): if len(fragments) < 2: return fragments # 找到最左侧的碎片(左侧空白最大) left_margins = [left[i] for i in fragments] start_idx = fragments[np.argmax(left_margins)] sorted_frags = [start_idx] remaining = set(fragments) remaining.remove(start_idx) while remaining: current = sorted_frags[-1] best_match = None best_score = -1 for candidate in remaining: # 计算匹配分数 score = 0 # 比较当前碎片的右边缘和候选碎片的左边缘 current_right = img[current][:, -1] # 当前碎片的最后一列 candidate_left = img[candidate][:, 0] # 候选碎片的第一列 # 计算像素匹配度 match_count = np.sum(current_right == candidate_left) # 增强文字区域的匹配权重 text_match = np.sum((current_right == 0) & (candidate_left == 0)) score = match_count + 2 * text_match if score > best_score: best_score = score best_match = candidate if best_match is not None: sorted_frags.append(best_match) remaining.remove(best_match) else: # 如果没有找到匹配,随机选择一个 sorted_frags.append(remaining.pop()) return sorted_frags # 对每行进行排序 sorted_rows = [] for row_id, fragments in enumerate(row_assignments): if not fragments: sorted_rows.append([]) continue sorted_frags = sort_fragments_in_row(fragments) sorted_rows.append(sorted_frags) # 可视化行排序结果 row_img = img[sorted_frags[0]] for frag_id in sorted_frags[1:]: row_img = np.hstack((row_img, img[frag_id])) plt.figure(figsize=(15, 3)) plt.imshow(row_img, cmap='gray') plt.title(f'行 {row_id} 排序结果') plt.axis('off') plt.show() # 人工干预:调整行内顺序 manual_adjust = input(f"行 {row_id} 排序是否正确?(y/n): ") if manual_adjust.lower() == 'n': print("当前顺序:", sorted_frags) new_order = list(map(int, input("输入正确顺序(用空格分隔): ").split())) sorted_rows[row_id] = new_order # 10. 行间排序 def sort_rows(rows): if len(rows) < 2: return rows # 找到最顶部的行(顶部空白最大) top_margins = [] for row in rows: if not row: top_margins.append(0) continue row_img = img[row[0]] for y in range(row_img.shape[0]): if np.any(row_img[y] == 0): # 找到第一个文字像素 top_margins.append(y) break else: top_margins.append(row_img.shape[0]) start_idx = np.argmax(top_margins) sorted_rows = [rows[start_idx]] remaining = set(range(len(rows))) remaining.remove(start_idx) while remaining: current_row_idx = sorted_rows[-1] if not current_row_idx: # 空行 break current_bottom = img[current_row_idx[0]][-1] # 当前行第一个碎片的最后一行 best_match = None best_score = -1 for candidate_idx in remaining: candidate_row = rows[candidate_idx] if not candidate_row: continue candidate_top = img[candidate_row[0]][0] # 候选行第一个碎片的第一行 # 计算匹配分数 score = 0 # 比较当前行的底部和候选行的顶部 match_count = np.sum(current_bottom == candidate_top) # 增强文字区域的匹配权重 text_match = np.sum((current_bottom == 0) & (candidate_top == 0)) score = match_count + 2 * text_match if score > best_score: best_score = score best_match = candidate_idx if best_match is not None: sorted_rows.append(rows[best_match]) remaining.remove(best_match) else: # 如果没有找到匹配,随机选择一个 sorted_rows.append(rows[remaining.pop()]) return sorted_rows # 行间排序 final_row_order = sort_rows(sorted_rows) # 11. 可视化行间排序结果 print("行间排序结果预览...") for i, row in enumerate(final_row_order): if not row: continue row_img = img[row[0]] for frag_id in row[1:]: row_img = np.hstack((row_img, img[frag_id])) plt.figure(figsize=(15, 3)) plt.imshow(row_img, cmap='gray') plt.title(f'最终排序 - 行 {i}') plt.axis('off') plt.show() # 人工干预:调整行顺序 manual_adjust = input("行间排序是否正确?(y/n): ") if manual_adjust.lower() == 'n': print("当前行顺序:", [i for i in range(len(final_row_order))]) new_order = list(map(int, input("输入正确行顺序(用空格分隔): ").split())) final_row_order = [final_row_order[i] for i in new_order] # 12. 最终拼接与结果输出 # 拼接最终图像 full_image = None for row in final_row_order: if not row: continue row_img = img[row[0]] for frag_id in row[1:]: row_img = np.hstack((row_img, img[frag_id])) if full_image is None: full_image = row_img else: # 在行之间添加空白分隔(可选) separator = np.ones((10, row_img.shape[1]), dtype=row_img.dtype) # 10像素高的空白行 full_image = np.vstack((full_image, separator)) full_image = np.vstack((full_image, row_img)) # 保存结果 if full_image is not None: # 转换为0-255范围 full_image = (1 - full_image) * 255 # 反转:0变为255(白色),1变为0(黑色) full_image = full_image.astype(np.uint8) final_img = Image.fromarray(full_image) final_img.save('result4.png') print("最终拼接结果已保存为 'result4.png'") else: print("错误: 无法拼接图像") # 13. 输出碎片顺序表格 def create_result_table(final_row_order): table = [] for row in final_row_order: if row: table.append(row) else: table.append([-1] * col) # 空行占位符 # 确保表格有11行 while len(table) < row: table.append([-1] * col) return np.array(table) result_table = create_result_table(final_row_order) print("碎片顺序表格:") print(result_table) # 保存表格到CSV pd.DataFrame(result_table).to_csv('result4.csv', index=False, header=False) print("碎片顺序表格已保存为 'result4.csv'") # 14. 保存未分配碎片 if unassigned: print(f"未分配的碎片: {unassigned}") with open('unassigned_fragments.txt', 'w') as f: f.write("未分配的碎片:\n") f.write(", ".join(map(str, unassigned))) else: print("所有碎片都已成功分配") 优化代码,11*19张碎纸片(大小相同),我们先分成16类,后续再通过人工调整成11类(对应11行),先对英文字母的空白行掩码处理,再提取特征(对于同一行匹配有着强烈作用的,可以忽略次要作用),然后聚类成16类,此时给出16张照片每张照片包含碎纸片及其编码,最后通过人工调控作用,聚为11类,在聚为11类之后进行行内排序(可以依照灰度变化的斜率,当斜率大于一定值为空白行和字的交界处,从而用于判断提取空白行、文字位置特征,边缘像素的匹配),最后同样给出排序结果图以及人工调控的选项(图上要标明碎片的编号),最后对这些行排序,建立上下边界匹配模型,得出上下位置序列,最后同样可以人工调控序列
08-12
from ftplib import all_errors import os from matplotlib import use from multiprocessing import Process, freeze_support import matplotlib.pyplot as plt from matplotlib import style import xlrd import datetime import numpy as np from matplotlib.gridspec import GridSpec # import test_times # import version import sys import re import time import json from matplotlib.ticker import MaxNLocator, MultipleLocator import threading import math import matplotlib.ticker as ticker # import matplotlib.patches as patches from pylab import mpl os.environ["MPLCONFIGDIR"] = "/Applications/plots/matplotlib_cache" use("Agg") def version(versio): print(">>> Script Name: " + "\033[0:32mReport Plots\033[0m") print(">>> Script version: " + f"\033[0:33m{versio}\033[0m") print(" ") def read_by_chart(): config_path2 = "/Applications/plots/Frameworks/config.ini" with open(config_path2, "r") as f: config_info = f.read() config = [] for i in config_info.split("\n")[:-1]: pre_config = list(i.split(",")) config.append(pre_config) return config def select_chart(): print(" " + "-" * 78) by_chart = read_by_chart()[0] print(" \033[0:33mopp\033[0m") print(" \033[0:32mlocal \033[0m ", "1.", by_chart[1].split(" ")[0], " 2.", by_chart[2].split(" ")[0], " 3.", by_chart[3].split(" ")[0], " 4.", by_chart[4].split(" ")[0], " 5.", by_chart[5].split(" ")[0]) print(" \033[0:31minsight\033[0m", ) print(" " + "-" * 78) print() def time_delte(ti): yyr = {1: 0, 2: 31, 3: 60, 4: 91, 5: 121, 6: 152, 7: 182, 8: 213, 9: 244, 10: 274, 11: 305, 12: 335} yy = {1: 0, 2: 31, 3: 59, 4: 90, 5: 120, 6: 151, 7: 181, 8: 212, 9: 243, 10: 273, 11: 304, 12: 334} t = ti.split() if "/" in t[0]: s = t[0].split("/") elif "-" in t[0]: s = t[0].split("-") y = int(s[0]) y_ = y - 1 if (y % 4 == 0 and y % 100 != 0) or (y % 400 == 0): # 判断闰年条件 ys = 365 ms = yyr else: if (y_ % 4 == 0 and y_ % 100 != 0) or (y_ % 400 == 0): ys = 366 else: ys = 365 ms = yy ss = t[1].split(":") if len(ss) < 3: ss.append(00) y_times = (int(s[0]) * ys + ms[int(s[1])] + int(s[2])) * 24 * 3600 h_times = int(ss[0]) * 3600 + int(ss[1]) * 60 + int(ss[2]) return y_times + h_times def is_number(s_su): try: float(s_su) return True except all_errors: return False def ret_number(s_ua): try: return int(s_ua) except all_errors: return None class Reportopp: def __init__(self): self.colar_list1 = None self.test_result = None self.endtime = None self.start_time = None self.test_iterm = None self.ax2 = None self.enter_limit = None self.ax1 = None self.version = None self.test_date_list = None self.station_name = None self.station = None self.station_testlist = [] self.data_csv = [] self.up_limt = [] self.down_limt = [] self.line_type = None self.mark = None self.if_testtime = 1 self.dict_summary = [] def read_test_station(self): pre_path = '/Applications/plots' path_f = pre_path + "/Frameworks/" config_path1 = path_f + "data_Type.json" with open(config_path1, "r") as f: cvs_data_type = json.load(f, )[self.station_name] mark_path = path_f + "config.ini" with open(mark_path, "r") as f: f_read = f.readlines() self.mark = f_read[-2].split("\n")[0] self.if_testtime = f_read[-1] self.enter_limit = f_read[-3].split("\n")[0] if "insight" == cvs_data_type: config_path1 = path_f + "testlist.xls" elif "local" == cvs_data_type: config_path1 = path_f + "testlist_local.xls" book = xlrd.open_workbook(config_path1) sheet1 = book.sheets()[0] nrows = sheet1.nrows for i in range(1, nrows): check_station = sheet1.row_values(i) for j in range(check_station.count("")): check_station.remove("") if check_station: if check_station[1] == self.station_name: self.station = check_station[0] self.station_testlist = check_station[2:] break print(" %s" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")), "初始化完成。。。") def data_read(self, path_csv): self.data_csv = [] try: with open(path_csv, 'r', newline='', encoding='utf-8', errors='replace') as csv_date: ss_read = csv_date.readlines() except all_errors: with open(path_csv, 'r', newline='', encoding='gbk', errors='replace') as csv_date: ss_read = csv_date.readlines() check_upper = 0 for i in range(len(ss_read)): self.data_csv.append( ss_read[i].split("\t")[0].split(",")[:-1] + ss_read[i].split("\t")[0].split(",")[-1].split("\r")) if check_upper == 0: if "upper limit" in ss_read[i].split("\t")[0].split(",")[0].lower(): up_q = i check_upper = 1 self.test_iterm = self.data_csv[1] test_qty = len(self.data_csv) test_qty_start = 0 for j in self.data_csv: if "Measurement Unit" in j[0]: test_qty_start = self.data_csv.index(j) + 1 break self.test_date_list = range(test_qty_start, test_qty) self.up_limt = ss_read[up_q].split(",") self.down_limt = ss_read[up_q + 1].split(",") self.station_name = self.data_csv[0][0] wipass = self.data_csv[0][1] if ":" in wipass: self.version = wipass.split(":")[-1] else: self.version = wipass.split("-")[0] def plot_run(self, x, s, lw, c, ls, marker): self.ax1.plot(x, s, lw=lw, c=c, ls=ls, marker=marker) def test_cpk(self, test_item, s_index): test_data = self.data_csv try: test_rowm = self.test_iterm.index(test_item) test_value_list = [float(test_data[i][test_rowm]) for i in self.test_date_list if is_number(test_data[i][test_rowm]) and float(test_data[i][test_rowm]) != 99999] if test_value_list: avg = sum(test_value_list) / len(test_value_list) fang = [(i - avg) ** 2 for i in test_value_list] sigm = (sum(fang) / (len(test_value_list) - 1)) ** 0.5 up_limt = self.up_limt[test_rowm] down_limt = self.down_limt[test_rowm] if sigm == 0: cp, cpk_, cpu, cpl, cpk = "NA", "NA", "NA", "NA", "NA" else: if "NA" not in up_limt and "NA" not in down_limt: c = (float(up_limt) + float(down_limt)) / 2 t = (float(up_limt) - float(down_limt)) / 2 ca = (avg - c) / t cp = t / 3 / sigm cpk_ = round(cp * abs(1 - ca), 3) cpu = round((float(up_limt) - avg) / (3 * sigm), 3) cpl = round((avg - float(down_limt)) / (3 * sigm), 3) cpk = round(min(cpk_, cpl, cpu), 3) elif "NA" not in up_limt and "NA" in down_limt: cpk = round((float(up_limt) - avg) / (3 * sigm), 3) elif "NA" in up_limt and "NA" not in down_limt: cpk = round((avg - float(down_limt)) / (3 * sigm), 3) else: cpk = "NA" try: ss_s = math.log10(abs(cpk)) if ss_s > 6: cck = int(ss_s) cpk_1 = round(cpk / (10 ** cck), 2) cpk = f"{cpk_1}E{cck}" except all_errors: pass return round(avg, 3), round(sigm, 3), cpk, max(test_value_list), min(test_value_list) else: return "NA", "NA", "NA", "NA", "NA" except all_errors: return "NA", "NA", "NA", "NA", "NA" @staticmethod def test_only1_cpk(sigm, avg, uplimt, downlimt): try: up_limt = float(uplimt) down_limt = float(downlimt) c = (float(up_limt) + float(down_limt)) / 2 t = (float(up_limt) - float(down_limt)) / 2 ca = (avg - c) / t if sigm == 0: cp, cpk_, cpu, cpl, cpk = "NA", "NA", "NA", "NA", "NA" else: cp = t / 3 / sigm cpk_ = round(cp * abs(1 - ca), 2) cpu = round((float(up_limt) - avg) / (3 * sigm), 2) cpl = round((avg - float(down_limt)) / (3 * sigm), 2) cpk = round(min(cpk_, cpl, cpu), 2) except all_errors: cpl, cpu, cpk = 0, 0, 0 return cpl, cpu, cpk def colms(self, path_p, byshow, station): ng_flot = 0 if byshow == "Slot Number": try: byshow_index = self.test_iterm.index("Slot Number") except all_errors: try: byshow_index = self.test_iterm.index("tc=Slot Number tech=Unit") except all_errors: try: byshow_index = self.test_iterm.index("tech=Unit;tc=Slot Number") except all_errors: try: byshow_index = self.test_iterm.index("Test Pass/Fail Status") print(f" 未在csv文件找到 {byshow}") except all_errors: print(f" 未在csv文件找到 {byshow}") sys.exit() byshow_index1 = self.test_iterm.index("Station ID") else: try: byshow_index = self.test_iterm.index(str(byshow)) byshow_index1 = "" except all_errors: if byshow == "Version": try: byshow_index = self.test_iterm.index("WiPAS-Version") except all_errors: try: byshow_index = self.test_iterm.index("Test Pass/Fail Status") print(f" 未在csv文件找到 {byshow}") except all_errors: print(f" 未在csv文件找到 {byshow}") sys.exit() elif byshow == "Serial Number": try: byshow_index = self.test_iterm.index("SerialNumber") except all_errors: try: byshow_index = self.test_iterm.index("Serial Number") except all_errors: try: byshow_index = self.test_iterm.index("Test Pass/Fail Status") print(f" 未在csv文件找到 {byshow}") except all_errors: print(f" 未在csv文件找到 {byshow}") sys.exit() else: try: byshow_index = self.test_iterm.index("Test Pass/Fail Status") print(f" 未在csv文件找到 {byshow}") except all_errors: print(f" 未在csv文件找到 {byshow}") sys.exit() byshow_index1 = "" plt_idix = 0 lpdet_list = [] for test_name in self.station_testlist: ax_list = [] print(" %s" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")), self.station, plt_idix, test_name) test_index = [] test_name_list = [] for i in self.test_iterm: i_new = i.lower() if re.findall(test_name.lower(), i_new, flags=0): if "LPDET_Sense" in i: lpdet_list.append(self.test_iterm.index(i)) index_ = self.test_iterm.index(i) test_index.append(index_) test_name_list.append(i) if "starttime" in i_new or "start time" in i_new: self.start_time = self.test_iterm.index(i) if "endtime" in i_new or "stop time" in i_new: self.endtime = self.test_iterm.index(i) if "test pass" in i_new or "fail status" in i_new: self.test_result = self.test_iterm.index(i) x = list(i for i in range(1, len(test_index) + 1)) if len(test_index) == 2: len_1, len_2 = 0.9, 0.1 else: len_1, len_2 = 1 - len(test_index) * 0.03, len(test_index) * 0.03 test_only1 = [] na_date = 0 fail_cont = 0 plt_colar = ["#1431F5", "#72F64A", "#74F9FD", "#FEFB54", "#EA51F7", "#EE8731", "#74247B", "#92693B", "#000000"] text_colar_ = {"#1431F5": "w", "#72F64A": "k", "#74F9FD": "k", "#FEFB54": "k", "#EA51F7": "w", "#EE8731": "k", "#74247B": "w", "#92693B": "w", "#000000": "w"} dict_i = [] dis_onliny = {} test_time_colm = [] apple_pass_colm = [] upper_limit = [] lower_limit = [] for i in test_index: if "NA" not in self.up_limt[i] and "NA" not in self.down_limt[i]: upper_limit.append(float(self.up_limt[i])) lower_limit.append(float(self.down_limt[i])) elif "NA" not in self.up_limt[i] and "NA" in self.down_limt[i]: upper_limit.append(float(self.up_limt[i])) lower_limit.append(None) elif "NA" in self.up_limt[i] and "NA" not in self.down_limt[i]: upper_limit.append(None) lower_limit.append(float(self.down_limt[i])) else: upper_limit.append(None) lower_limit.append(None) if len(x) != 1: fig = plt.figure(figsize=(14, 7.5), dpi=144) gs = GridSpec(100, 100) plt.axis('off') self.ax1 = fig.add_subplot(gs[4:55, 4:75]) ax_list.append(self.ax1) fig = plt.gcf() fig.set_facecolor('#FFFFFF') self.ax1.patch.set_facecolor("#FFFFFF") self.ax1.grid(c='grey', lw=0.8) self.ax1.yaxis.grid(True, which='minor', ls='--', c='#787A78', lw=0.4) self.ax1.xaxis.grid(True, which='minor', ls='--', c='#787A78', lw=0.4) plt.gca().xaxis.set_major_locator(MaxNLocator(integer=True)) plt.title(test_name, fontsize=17, fontweight='bold', style="normal") else: fig = plt.figure(figsize=(14.819, 4.916), dpi=144) plt.subplots_adjust(left=0.0006, right=0.999, top=0.997, bottom=0.005) gs = GridSpec(100, 100) plt.axis('off') style.use("default") ax_kuang = fig.add_subplot(gs[0:100, 0:100]) ax_kuang.tick_params(bottom=False, top=False, left=False, right=False) plt.xticks([]) plt.yticks([]) self.ax1 = fig.add_subplot(gs[10:90, 16:75]) plt.title(test_name, fontsize=17, fontweight='bold', style="normal") ss_max = [] ss_min = [] ylimt_up = [] ylimt_down = [] s_upper_limit = [float(i) for i in upper_limit if i or ret_number(i) == 0] s_lower_limit = [float(i) for i in lower_limit if i or ret_number(i) == 0] test_value_total = [] plot_list = [] for test_i in self.test_date_list: try: sad = self.data_csv[test_i] result = sad[self.test_result].lower() if "pass" in result: test_time = time_delte(sad[self.endtime]) - time_delte( sad[self.start_time]) test_time_colm.append(test_time) if "pass" != result: apple_pass_colm.append(test_time) except all_errors: pass s = [] true_index = [] for i in test_index: test_value = str(self.data_csv[test_i][i]) if test_value == "NA" or not is_number(test_value) or test_value == "": na_date += 1 s.append(None) elif float(test_value) == 99999: fail_cont += 1 else: s.append(float(test_value)) true_index.append(test_index.index(i)) if upper_limit[test_index.index(i)]: if s_upper_limit and float(test_value) > float(upper_limit[test_index.index(i)]): fail_cont += 1 if lower_limit[test_index.index(i)]: if s_lower_limit and float(lower_limit[test_index.index(i)]) > float(test_value): fail_cont += 1 test_value_total.append(float(test_value)) if byshow_index1 == "": byshow_value = self.data_csv[test_i][byshow_index] else: byshow_value = self.data_csv[test_i][byshow_index1] + " " + self.data_csv[test_i][byshow_index] if plt_idix == 0: self.dict_summary.append(byshow_value) if byshow_value not in dict_i: dict_i.append(byshow_value) if len(dict_i) > len(plt_colar): ss1 = len(dict_i) // len(plt_colar) colar_true = plt_colar * ss1 s_then = len(dict_i) % len(plt_colar) self.colar_list = colar_true + plt_colar[:s_then] else: self.colar_list = plt_colar[:len(dict_i)] if s_upper_limit: list(set(s_upper_limit)) if s_upper_limit == [0]: max_upper_limit = None else: max_upper_limit = max(s_upper_limit) else: max_upper_limit = None if s_lower_limit: list(set(s_lower_limit)) if s_lower_limit != [0]: min_lower_limit = min(s_lower_limit) else: min_lower_limit = None else: min_lower_limit = None if len(x) != 1: plot_qty = len(x) plot_qq = len(s) if plot_qty == plot_qq and plot_qty >= 2: if lpdet_list == test_index: s = [i for i in reversed(s)] st = threading.Thread(target=self.plot_run, args=(x, s, 1.3, self.colar_list[dict_i.index(byshow_value)], '-', '.')) st.start() plot_list.append(st) elif plot_qty != plot_qq and plot_qty >= 2: y = [] if true_index: for i in range(max(true_index) + 1): if i in true_index: y.append(s[true_index.index(i)]) else: true_index.append(i) y.append(None) true_index.sort() true_index.append(max(true_index) + 1) true_index.remove(0) st = threading.Thread(target=self.plot_run, args=( true_index, y, 1.3, self.colar_list[dict_i.index(byshow_value)], '-', '.')) st.start() plot_list.append(st) else: if s: dis_onliny.setdefault(byshow_value, []).append(s) test_only1 += s ng_flot_check = "OK" if not test_name_list: test_name_list = ["1", "2"] x = [1, 2] ss_max = [2, 2] ss_min = [0, 0] test_index = [1, 2] upper_limit = [None, None] lower_limit = [None, None] mpl.rcParams["font.sans-serif"] = ["Arial Unicode MS"] mpl.rcParams["axes.unicode_minus"] = False self.ax1.text(1.3, 1.2, "请确认关键字是否正确", fontsize=35, family="Arial Unicode MS", c="r") print(" 请确认关键字是否正确") ng_flot += 1 ng_flot_check = "NG" else: for i in range(test_name_list.count(1)): test_name_list.remove(1) for i in range(test_name_list.count(2)): test_name_list.remove(2) max_, min_ = 1, 0 if len(x) == 1: test_only1 = [i for i in test_only1 if is_number(i)] try: s1 = (max(upper_limit) - min(lower_limit)) / 95 except all_errors: s1 = (max(test_only1) - min(test_only1)) * 1.1 / 95 s2 = int((max(test_only1) - min(test_only1)) / s1) bins = np.linspace(min(test_only1), max(test_only1), s2) if len(dict_i) == 1: if 1: n, bins, _patches = self.ax1.hist(test_only1, bins=bins, align="mid", facecolor="b", alpha=0.8) n_max = max(bins) n_min = min(bins) nm_max = 0 for i_n in n: if int(i_n) > nm_max: nm_max = int(i_n) else: nm_max = nm_max y_max = nm_max max_, min_ = y_max * 1.2, 0 else: y_limit = [] keys_only = [] vals = [] colors_list = [] if len(test_only1) > len(plt_colar): ss1 = len(test_only1) // len(plt_colar) colar_true = plt_colar * ss1 s_then = len(test_only1) % len(plt_colar) self.colar_list = colar_true + plt_colar[:s_then] else: self.colar_list = plt_colar[:len(dict_i)] for key, value in dis_onliny.items(): if key not in keys_only: keys_only.append(key) ss1 = [] for i in value: if is_number(i[0]): ss1.append(float(i[0])) y_limit.append(len(value)) vals.append(ss1) colors = self.colar_list[keys_only.index(key)] colors_list.append(colors) n, bins, _patches = self.ax1.hist(vals, bins=bins, color=colors_list, histtype="stepfilled", alpha=0.8) n_max = max(bins) n_min = min(bins) nm_max = [] for i_n in n: nn_max = max(i_n) nm_max.append(nn_max) y_max = max(nm_max) if y_max <= 0: y_max = 1 max_, min_ = int(y_max) * 1.2, 0 if None not in upper_limit and None not in lower_limit: spec_limit = (upper_limit[0] - lower_limit[0]) low_ = float(lower_limit[0]) uper_ = float(upper_limit[0]) self.ax1.plot([low_, low_], [0, max_], c="red", lw=2, ls="-") self.ax1.plot([uper_, uper_], [0, max_], c="red", lw=2, ls="-") plt.xlim(low_ - spec_limit * 0.02, uper_ + spec_limit * 0.02) elif None in upper_limit and None not in lower_limit: spec_limit = (n_max * 1.3 - lower_limit[0]) low_ = float(lower_limit[0]) self.ax1.plot([low_, low_], [0, max_], c="red", lw=2, ls="-") plt.xlim(lower_limit[0] - spec_limit * 0.02, n_max * 1.1 + spec_limit * 0.02) elif None not in upper_limit and None in lower_limit: spec_limit = (upper_limit[0] - n_min * 0.7) uper_ = float(upper_limit[0]) self.ax1.plot([uper_, uper_], [0, max_], c="red", lw=2, ls="-") plt.xlim(n_min * 0.9 - spec_limit * 0.02, upper_limit[0] + spec_limit * 0.02) else: spec_limit = (n_max * 1.3 - n_min * 0.7) plt.xlim(n_min * 0.9 - spec_limit * 0.02, n_max * 1.1 + spec_limit * 0.02) plt.ylabel('Count') # 绘制y轴 self.ax1.grid(axis="y") fig.set_facecolor('#FFFFFF') self.ax1.patch.set_facecolor("w") plt.ylim(0, y_max * 1.2) ax4 = fig.add_subplot(gs[10:90, 1:11]) ax_list.append(ax4) ax4.tick_params(bottom=False, top=False, left=False, right=False) plt.xlim(0, 100) plt.ylim(0, 100) test_only2 = [i for i in test_only1 if is_number(i)] sigm, avg = round(np.std(test_only2), 2), round(np.mean(test_only2), 2) cp, cpu, cpk = self.test_only1_cpk(sigm, avg, upper_limit[0], lower_limit[0]) plt.rcParams['font.sans-serif'] = "Helvetica" fonn = 10 ax4.text(1, 95, f"Data Count: {len(test_only1)}", fontsize=fonn) ax4.text(1, 87, f"Max: {round(max(test_only2), 2)}", fontsize=fonn) ax4.text(1, 79, f"Min: {round(min(test_only2), 2)}", fontsize=fonn) ax4.text(1, 72, f"Mean: {avg}", fontsize=fonn) ax4.text(1, 65, f"Std. Dev.: {sigm}", fontsize=fonn) ax4.text(1, 58, f"Cpu: {cpu}", fontsize=fonn) ax4.text(1, 51, f"Cpl: {cp}", fontsize=fonn) ax4.text(1, 44, f"Cpk: {cpk}", fontsize=fonn) ax4.text(1, 37, f"Upper Limit: {upper_limit[0]}", fontsize=fonn) ax4.text(1, 30, f"Lower Limit: {lower_limit[0]}", fontsize=fonn) ax4.text(1, 23, f"NA Count: {na_date}", fontsize=fonn) ax4.text(1, 16, f"Failure Count: {fail_cont} ({round(fail_cont / len(test_only1), 2)}%)", fontsize=fonn) plt.axis("off") x_s = spec_limit if x_s < 6: sim_x = 0.25 elif 6 <= x_s <= 12: sim_x = 0.5 elif 12 < x_s < 60: sim_x = 1 else: sim_x = int(x_s // 40) + 1 x_xlim = sim_x * 4 xminorLocator = MultipleLocator(sim_x) self.ax1.xaxis.set_minor_locator(xminorLocator) xmajorLocator = MultipleLocator(x_xlim) self.ax1.xaxis.set_major_locator(xmajorLocator) y_s = max_ - min_ if y_s < 0: y_s = 0 sdsd = int(y_s // 20) if sdsd == 0: ymajorLocator = MultipleLocator(1) self.ax1.yaxis.set_major_locator(ymajorLocator) elif sdsd > 8: ymajorLocator = MultipleLocator((int(sdsd // 10) + 1) * 12) self.ax1.yaxis.set_major_locator(ymajorLocator) else: ymajorLocator = MultipleLocator(sdsd + 2) self.ax1.yaxis.set_major_locator(ymajorLocator) ax5 = fig.add_subplot(gs[8:94, 77:99]) ax_list.append(ax5) ax5.tick_params(bottom=False, top=False, left=False, right=False) ax5.spines['right'].set_visible(False) ax5.spines['top'].set_visible(False) ax5.spines['left'].set_visible(False) ax5.spines['bottom'].set_visible(False) plt.xticks([]) plt.yticks([]) plt.xlim(0, 100) plt.ylim(0, 100) plt.rcParams['font.sans-serif'] = "Helvetica" ax5.patch.set_facecolor("#FFFFFF") ax5.text(2, 95, byshow, fontsize=10) if len(dict_i) > 15: dict_i = dict_i[:15] s = [] for i in dict_i: s.append(i) dict_i.sort() color_ = self.colar_list for i in dict_i: ax5.text(18, 90.8 - dict_i.index(i) * 6, i, fontsize=8) cc = color_[s.index(i)] plt.plot([5, 14], [91.8 - dict_i.index(i) * 6, 91.8 - dict_i.index(i) * 6], c=cc, lw=11, solid_capstyle='round', ls="-") ax5.text(5, 90.8 - dict_i.index(i) * 6, self.dict_summary.count(i), fontsize=8, c=text_colar_[cc]) plt.grid(c='black', linestyle='--', lw=0.3) plt.grid(True) else: if ng_flot_check != "NG": if test_value_total: max_test_value_total = max(test_value_total) min_test_value_total = min(test_value_total) else: max_test_value_total = None min_test_value_total = None plt.xlim(len_1, len(test_index) + len_2) self.ax1.tick_params(labelsize=9) if lpdet_list == test_index: upper_limit.sort() lower_limit.sort(reverse=True) if None not in upper_limit and None not in lower_limit and upper_limit and lower_limit: if self.enter_limit == "low_and_upper_limit:1": try: s_max, s_min = max(upper_limit), min(lower_limit) except all_errors: s_max, s_min = upper_limit[0], lower_limit[0] else: s_max, s_min = max(max(upper_limit), max_test_value_total), min(min(lower_limit), min_test_value_total) self.ax1.plot(x, upper_limit, c='r', lw=2, ls='-', markersize=8) self.ax1.plot(x, lower_limit, c='r', lw=2, ls='-', markersize=8) else: if None in upper_limit: self.ax1.plot(x, upper_limit, c='r', lw=2, ls='-', markersize=8, marker='.', markeredgewidth=1) else: self.ax1.plot(x, upper_limit, c='r', lw=2, ls='-', markersize=8) if None in lower_limit: self.ax1.plot(x, lower_limit, c='r', lw=2, ls='-', markersize=8, marker='.', markeredgewidth=1) else: self.ax1.plot(x, lower_limit, c='r', lw=2, ls='-', markersize=8) s_max_ = [i for i in [max_upper_limit, max_test_value_total, min_test_value_total, min_lower_limit] if is_number(i)] s_min_ = [i for i in [max_upper_limit, max_test_value_total, min_test_value_total, min_lower_limit] if is_number(i)] if not s_max_: s_max_ = [0] if not s_min_: s_min_ = [0] delta_u = max(s_max_) - min(s_min_) s_max, s_min = max(s_max_), min(s_min_) if not is_number(max_upper_limit) and not is_number(min_lower_limit): s_max = max(s_max_) + 2 * delta_u s_min = min(s_min_) - 2 * delta_u else: if not is_number(max_upper_limit): s_max = max(s_max_) + 0.2 * delta_u if not is_number(min_lower_limit): s_min = min(s_min_) - 0.2 * delta_u ss_max.append(s_max) ss_min.append(s_min) ss_max = [i for i in ss_max if is_number(i)] ss_min = [i for i in ss_min if is_number(i)] yy_max = max(ss_max) yy_min = min(ss_min) yy_spec = yy_max - yy_min if yy_max + yy_spec * 0.2 > yy_min - yy_spec * 0.1: plt.ylim(yy_min - yy_spec * 0.15, yy_max + yy_spec * 0.15) ylimt_up.append(yy_max + yy_spec * 0.1) ylimt_down.append(yy_min - yy_spec * 0.1) elif yy_max + yy_spec * 0.2 < yy_min - yy_spec * 0.1: plt.ylim(yy_max + yy_spec * 0.15, yy_min - yy_spec * 0.15) ylimt_up.append(yy_min - yy_spec * 0.1) ylimt_down.append(yy_max + yy_spec * 0.1) else: plt.ylim(yy_max + 5, yy_min - 5) ylimt_up.append(yy_max + 5) ylimt_down.append(yy_min - 5) if upper_limit and lower_limit: y_s = max(ss_max) - min(ss_min) else: upper_limit22 = [float(i) for i in upper_limit if i] lower_limit22 = [float(i) for i in lower_limit if i] if upper_limit22 and lower_limit22: y_s = max(max_, max(upper_limit22)) - min(min_, min(lower_limit22)) elif not upper_limit22 and not lower_limit22: y_s = yy_max - yy_min elif not upper_limit22 and lower_limit22: y_s = max_ - min(min_, min(lower_limit22)) elif upper_limit22 and not lower_limit22: y_s = max(max_, max(upper_limit22)) - min_ else: y_s = 0 else: plt.ylim(0, 3) plt.xlim(0, 5) y_s = 5 if ylimt_up: y_s = max(ylimt_up) - min(ylimt_down) if y_s <= 0.5: y_1 = round(y_s / 5, 2) elif 0.5 < y_s <= 1: y_1 = round(y_s / 5, 1) elif 1 < y_s <= 3: y_1 = 0.25 elif 3 < y_s <= 5: y_1 = 0.5 elif 5 < y_s < 10: y_1 = 1 elif 10 <= y_s <= 20: y_1 = 2.5 elif 20 < y_s <= 25: y_1 = 5 else: y_1 = int(int(y_s / 5) // 5) * 5 y_2 = y_1 / 5 plt.xticks(x) try: ymajorLocator = MultipleLocator(y_1) yymajorLocator = MultipleLocator(y_2) self.ax1.yaxis.set_minor_locator(yymajorLocator) self.ax1.yaxis.set_major_locator(ymajorLocator) except all_errors: yys = float('{:.3f}'.format(y_s / 5)) ymajorLocator = MultipleLocator(yys) yymajorLocator = MultipleLocator(yys / 5) self.ax1.yaxis.set_minor_locator(yymajorLocator) self.ax1.yaxis.set_major_locator(ymajorLocator) x_s = len(x) if x_s < 6: sim_x = 0.25 elif 6 <= x_s <= 20: sim_x = 0.5 elif 20 < x_s <= 80: sim_x = 1 else: sim_x = int(x_s // 80) if sim_x <= 5: sim_x = 5 elif sim_x <= 10: sim_x = 10 elif sim_x <= 15: sim_x = 15 elif sim_x <= 20: sim_x = 20 elif sim_x <= 25: sim_x = 25 elif sim_x <= 30: sim_x = 30 elif sim_x <= 35: sim_x = 35 elif sim_x <= 40: sim_x = 40 elif sim_x <= 45: sim_x = 45 else: sim_x = 50 x_xlim = sim_x * 5 # xminorLocator = MultipleLocator(sim_x) # self.ax1.xaxis.set_minor_locator(xminorLocator) # xmajorLocator = MultipleLocator(x_xlim) x_x = [i for i in range(1, len(x), int(x_xlim))] + [len(x)] plt.xticks(x_x) plt.gca().xaxis.set_minor_locator(ticker.AutoMinorLocator(5)) # self.ax1.xaxis.set_major_locator(xmajorLocator) plt.subplots_adjust(left=0, right=1, top=0.995, bottom=0) ax11 = fig.add_subplot(gs[99:, :]) ax_list.append(ax11) ax11.axis("on") ax11.spines['right'].set_visible(False) ax11.spines['top'].set_visible(False) ax11.spines['left'].set_visible(False) ax11.spines['bottom'].set_visible(False) self.ax2 = fig.add_subplot(gs[59:98, 2:78]) ax_list.append(self.ax2) self.ax2.tick_params(bottom=False, top=False, left=False, right=False) self.ax2.spines['right'].set_visible(False) self.ax2.spines['top'].set_visible(False) self.ax2.spines['left'].set_visible(False) self.ax2.spines['bottom'].set_visible(False) plt.axis("on") plt.xticks([]) plt.yticks([]) print_1 = "# | Test Name" + " " * 275 + "| Average" + " " * 16 + "| Std. Est." + " " * 12 + "| Cpk" self.ax2.text(1, 92, "-" * 313, fontsize=7, c="grey") self.ax2.text(1, 95, print_1, fontsize=7, c="black") plt.xlim(0, 100) plt.ylim(0, 100) plt.rcParams['font.sans-serif'] = "Helvetica" axright = fig.add_subplot(gs[0:99, 76:100]) plt.xticks([]) plt.yticks([]) plt.grid(True) axright.tick_params(bottom=False, top=False, left=False, right=False) axright.spines['right'].set_visible(False) axright.spines['top'].set_visible(False) axright.spines['left'].set_visible(False) axright.spines['bottom'].set_visible(False) axright.patch.set_facecolor("#D2D3D3") ax5 = fig.add_subplot(gs[1:55, 77:99]) ax_list.append(ax5) ax5.tick_params(bottom=False, top=False, left=False, right=False) ax5.spines['right'].set_visible(False) ax5.spines['top'].set_visible(False) ax5.spines['left'].set_visible(False) ax5.spines['bottom'].set_visible(False) plt.xticks([]) plt.yticks([]) plt.xlim(0, 100) plt.ylim(0, 100) plt.rcParams['font.sans-serif'] = "Helvetica" ax5.patch.set_facecolor("#FFFFFF") ax5.text(2, 95, byshow, fontsize=10) if len(dict_i) > 20: dict_i = dict_i[:20] s = [] for i in dict_i: s.append(i) dict_i.sort() color_ = self.colar_list for i in dict_i: ax5.text(18, 90.8 - dict_i.index(i) * 4.5, i, fontsize=8) cc = color_[s.index(i)] plt.plot([5, 14], [91.8 - dict_i.index(i) * 4.5, 91.8 - dict_i.index(i) * 4.5], c=cc, lw=11, solid_capstyle='round', ls="-") ax5.text(5, 90.8 - dict_i.index(i) * 4.5, self.dict_summary.count(i), fontsize=8, c=text_colar_[cc]) plt.grid(c='black', linestyle='--', lw=0.3) plt.grid(True) if lpdet_list == test_index: test_name_list.sort(reverse=True) ax9 = fig.add_subplot(gs[70:, 77:]) ax_list.append(ax9) plt.axis("off") plt.xticks([]) plt.yticks([]) plt.xlim(0, 100) plt.ylim(0, 100) try: test_tine_put = f"{round(sum(test_time_colm) / len(test_time_colm), 2)}(s)" except all_errors: test_tine_put = "No PASS" self.station_name = station if (self.station_name == "S-COND" or self.station_name == "SCOND") and "_" in self.version: self.version = self.version.split("_")[0] elif (self.station_name == "A-COND" or self.station_name == "ACOND") and "_" in self.version: self.version = self.version.split("_")[0] elif " " in self.version.lower(): self.version = self.version.split(" ")[0] self.version = self.version.replace("\n", "") ax9.text(1, 100, f" Station Name: {self.station_name}", fontsize=10, c="k") ax9.text(1, 90, f"WiPAS Version: {self.version}", fontsize=10, c="k") if self.if_testtime == "test_time:1": ax9.text(1, 80, f" Test Time: {test_tine_put}", fontsize=10, c="k") ax9.text(1, 70, f" Test Count: {len(self.test_date_list)}", fontsize=10, c="k") if apple_pass_colm: ax9.text(1, 50, f"Apple Pass Count: {len(apple_pass_colm)}", fontsize=10, c="k") ax9.text(1, 60, f" Pass Count: {len(test_time_colm) - len(apple_pass_colm)}", fontsize=10, c="k") else: ax9.text(1, 60, f" Pass Count: {len(test_time_colm)}", fontsize=10, c="k") else: ax9.text(1, 80, f" Test Count: {len(self.test_date_list)}", fontsize=10, c="k") if apple_pass_colm: ax9.text(1, 60, f"Apple Pass Count: {len(apple_pass_colm)}", fontsize=10, c="k") ax9.text(1, 70, f" Pass Count: {len(test_time_colm) - len(apple_pass_colm)}", fontsize=10, c="k") else: ax9.text(1, 70, f" Pass Count: {len(test_time_colm)}", fontsize=10, c="k") if len(x) != 1: for i in test_name_list: test_name_list_index = test_name_list.index(i) test_iterm = i.lower() if test_name_list_index >= 9: ll = 2 else: ll = 4 limit_spec = (max(ss_max) - min(ss_min)) * 0.1 mark_cpk1, mark_cpk2 = min(ss_min) - limit_spec, max(ss_max) + limit_spec test_cpk_2 = self.test_cpk(i, "index_s") cpk_ = test_cpk_2[2] if str(cpk_) != "NA" and self.mark == "marker:1" and "E" not in str(cpk_): if 0 < cpk_ < 1.33: if "sens" not in test_iterm and "per" not in test_iterm and "ber" not in test_iterm and "margindb" not in test_iterm: self.ax1.plot( [test_name_list_index + 1, test_name_list_index + 1], [mark_cpk1, mark_cpk2], c="yellow", lw=14, ls='-', alpha=0.4) if test_name_list_index < 14: try: if 0 >= float(test_cpk_2[2]) or float(test_cpk_2[2]) >= 1.33: color = "black" else: if "sens" in test_iterm or "per" in test_iterm or "ber" in test_iterm or "margindb" in test_iterm: color = "black" else: color = "red" except all_errors: color = "black" font_size = 8 print_i = str(test_name_list.index(i) + 1) + ". " + " " * int(ll) + str(i) self.ax2.text(1, 87.5 - test_name_list.index(i) * 6.4, print_i, fontsize=font_size) self.ax2.text(77, 87.5 - test_name_list.index(i) * 6.4, test_cpk_2[0], fontsize=font_size) self.ax2.text(85.3, 87.5 - test_name_list.index(i) * 6.4, test_cpk_2[1], fontsize=font_size) self.ax2.text(92, 87.5 - test_name_list.index(i) * 6.4, test_cpk_2[2], fontsize=font_size, c=color) self.ax2.text(1, 85.8 - test_name_list.index(i) * 6.4, "-" * 275, fontsize=8, c="grey") if len(test_name_list) <= 14: for y in range(len(test_name_list), 14): self.ax2.text(1, 85.8 - y * 6.4, "-" * 275, fontsize=8, c="grey") for thread in plot_list: thread.join() plt.savefig(os.path.join(path_p, f'{plt_idix} {test_name}.png')) fig.clf() fig.clear() plt.close("all") plt_idix += 1 if ng_flot > 0: print(f" 关键词: {len(self.station_testlist)}, 失败: {ng_flot}") else: print(f" 关键词: {len(self.station_testlist)}") sys.exit() if __name__ == "__main__": freeze_support() versions = "3.0.1(20250107)" version(versions) select_chart() opp2 = Reportopp() read_by_chart = read_by_chart() while True: bychart_ = input(f" 第一步、请选择show类型, 输入对应的数字, 并回车 ({read_by_chart[1][1]})\n ") try: if 0 < int(bychart_) < 6: by_ = read_by_chart[0][int(bychart_)] print(" %s 您选择了:" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")), by_) break else: by_ = "" print(" %s 输入错误, 请输入对应数字" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) continue except all_errors: if bychart_ == "": by_ = "Serial Number" print(" %s 您选择了:" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")), "Serial Number") break else: by_ = "" print(" %s 输入错误, 请输入对应数字" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) continue print(" 第二步、请拉入文件夹或 csv 文件") while True: path_s = input(" =>").rstrip() if "\\" in path_s: path_ = path_s.replace("\\", "") elif path_s: path_ = path_s else: print(" %s 无法找到文件夹及 csv 文件, 已退出!" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) sys.exit(0) if os.path.isdir(path_): i_index = 0 path_list = os.listdir(path_) path_list.sort() for filename_i in path_list: if "csv" == filename_i.split(".")[-1]: filename = f"{path_}/{filename_i}" opp2.data_read(filename) opp2.read_test_station() filename_ = filename.split("/")[-1].split(".")[0] path2 = f"{path_}/plots/{filename_}-plots/" if not os.path.exists(path2): os.makedirs(path2) print(" %s" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")), filename) i_index += 1 print(f" %s 已读取第{i_index}个csv, 站位为 {opp2.station}" % ( datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) mp = Process(target=opp2.colms, args=[path2, by_, opp2.station]) mp.start() mp.join() elif os.path.isfile(path_): if "csv" in path_: start_time = time.time() new_path = "/".join(path_.split("/")[:-1]) opp2.data_read(path_) opp2.read_test_station() filename_ = path_.split("/")[-1].split(".")[0] path2 = f"{new_path}/plots/{filename_}-plots/" if not os.path.exists(path2): os.makedirs(path2) print(f" %s 已读取csv, 站位为 {opp2.station}" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) mp = Process(target=opp2.colms, args=[path2, by_, opp2.station]) mp.start() mp.join() del mp end_time = time.time() print(end_time - start_time) else: print(" %s 非 csv 文件!" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) print() print(" 处理完成。") 帮忙转RUST开发
10-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值