Skip to main content

6 posts tagged with "csdn"

View All Tags

· 4 min read

https://blog.csdn.net/wolanx/article/details/124599294

Intro

在尝试了FC,CNN等模型在 mnist 的练习后,使用 torchvision.models 的官方定义尝试运行 vgg16,resnet。 常见会出现以下错误:

  • RuntimeError: Given groups=1, weight of size [64, 3, 3, 3], expected input[64, 1, 28, 28] to have 3 channels, but got 1 channels instead
  • RuntimeError: Given input size: (512x1x1). Calculated output size: (512x0x0). Output size is too small

模型定义如下

# mnist cnn 不知道怎么写的,可以参考 https://github.com/wolanx/pii/blob/main/x10_ml/demo2-6_mnist/demo2-6.ipynb
dataset1 = torchvision.datasets.MNIST(root="./data", train=True, download=True, transform=transform)

model = torchvision.models.vgg16(pretrained=False, num_classes=10)
model = model.to(device)
print(model)
VGG(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU(inplace=True)
(2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): ReLU(inplace=True)
(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
...
)
(avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
(classifier): Sequential(
(0): Linear(in_features=25088, out_features=4096, bias=True)
(1): ReLU(inplace=True)
...
)
)

错误一

Given groups=1, weight of size [64, 3, 3, 3], expected input[64, 1, 28, 28] to have 3 channels, but got 1 channels instead

是说维度不同,mnist里的数据为单色28px28px,所以是(1, 28, 28),但vgg是面向RGB图像是三色的,需要把它变成RGB的3通道,但是显然要自己去搞这样的数据太麻烦。换个思路,查看vgg16的定义把vgg的features的第一层修改下

VGG(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) # 改这一层
(1): ReLU(inplace=True)

修改完如下

model = torchvision.models.vgg16(pretrained=False, num_classes=10)
model.features[0] = nn.Conv2d(1, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) # 这里的 conv2d(3,64) 改成了 conv2d(1,64)
model = model.to(device)
print(model) # 再次检查下
VGG(
(features): Sequential(
(0): Conv2d(1, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) # 改这一层
(1): ReLU(inplace=True)

到这一步,错误一就解决了,但紧接着会出现错误二

错误二

Given input size: (512x1x1). Calculated output size: (512x0x0). Output size is too small

这一步说的太抽象,先回顾下vgg16的几层模型,需要注意到,图像经过多次conv,而conv stride=1时,相当于多图像进行了缩小(一半),而 mnist 的像素为 28 28,缩小一半为 14 14,再 7 7,再 4 4,再 2 2,再 1 1,再就 0 * 0。而vgg层数还没运行到最后就没了。和错误一相似,一里是RGB不满足,错误二是像素不满足,同样要换样本不现实。简单处理就是,不要不断的缩小,减层可以,但是会破坏定义结构。但也可以是反卷积:

model = torchvision.models.vgg16(pretrained=False, num_classes=10)
model.features[0] = nn.Conv2d(1, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) # 不是 (3,64) (1,16),单通道再加小一点
model.features[2] = nn.ConvTranspose2d(16, 64, kernel_size=(3, 3), stride=(2, 2), padding=(0, 0), bias=False)
model = model.to(device)
print(model) # 再次检查下
VGG(
(features): Sequential(
(0): Conv2d(1, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) # 这里变了
(1): ReLU(inplace=True)
(2): ConvTranspose2d(16, 64, kernel_size=(3, 3), stride=(2, 2), bias=False) # 这里变了

这样就解决了像素太低问题,整个模型至少现在能跑了,但是如果有做train的话,发现error实在收敛太慢,还需要再优化下。

· 11 min read

https://blog.csdn.net/wolanx/article/details/123526472

Intro

在使用的相当一段时间的 threejsreact-three-fiber 后,在中文资料环境极其匮乏的情况下,做个极简·笔记式的分享。目标是能让大家在 最快 的速度上手,且 半·精通

Install

npm install three @react-three/fiber 官方文档

Demo1 - 全局概览

![](https://img-blog.csdnimg.cn/1845e0529fbf434388ba99b656eaf466.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAeXVqaWUuemhhbw==,size_13,color_FFFFFF,t_70,g_se,x_16 =250x)

这是一个短小精悍的 demo1,对着下面代码看解析

  • 物体 盒子:new BoxGeometry() 球:new SphereGeometry(.5, 32, 16)
  • 物体上色 <mesh geometry={ball} material={mtl1} /> 使用 mesh 将 物体和材料 捆绑
  • 光1 <ambientLight intensity={0.1} /> 环境光,四面八方的光,intensity 光的强度
  • 光2 <directionalLight /> 一束光 需要 color position:照射方向
  • 控制 OrbitControls 交互操作,鼠标 旋转:左键拖拽 平移:右键拖拽 放大:滚轮
  • 阴影 <ContactShadows /> 参数字面意思
  • 背景 <color attach='background' args={['#aaa']} /> 也可以用 css 解决
import React, { Suspense } from 'react'
import { BoxGeometry, MeshStandardMaterial } from 'three'
import { Canvas } from '@react-three/fiber'
import { ContactShadows, OrbitControls } from '@react-three/drei'

const ball = new BoxGeometry()
const mtl1 = new MeshStandardMaterial({ color: '#f00' })

export default function Demo () {
return (
<Canvas style={{ height: 800 }} camera={{ fov: 75, near: 0.1, far: 1000, position: [2, 1, 2] }}>
<Suspense fallback={null}>
<ambientLight intensity={0.1} />
<directionalLight color={'#fff'} intensity={1} position={[-3, 5, 5]} />
<mesh geometry={ball} material={mtl1} />
<OrbitControls makeDefault />
<ContactShadows rotation-x={Math.PI / 2} position={[0, -1.4, 0]} opacity={0.75} width={10} height={10} blur={2.6} far={2} />
<color attach='background' args={['#aaa']} />
</Suspense>
</Canvas>
)
}

如果你认真看完 demo1 每一处细节,你已经对 threejs 有了 50% 的认识了。。。后续就是对每个部分的展开和丰富

Demo2 - 文件加载

实际项目并不会像 demo1 中使用系统物体,通常是 外部文件,常见格式有 obj,gltf,glb,fbx 等。 demo2 从外部加载,并添加了一些常用 工具

  • 远程加载 useLoader(OBJLoader, src) 不同的格式,只需要切换加载类,快捷版:useFBX(src) useGLTF(src, true)
  • 坐标系 new AxesHelper(100) 方便查看三维世界
  • 性能工具 <Stats showPanel={0} parent={statRef} /> fps,需要通过 ref 来控制位置
  • 环境设置 <Environment /> 具体看代码 6个贴面(东南西北+上下),主要控制光反射
  • 错误抑制 <ErrorBoundary> 出错以后,可以把错误局限在这里面,不影响整体框架,应用了 antd 的,也可以自己实现或者不要
import React, { Suspense, useRef } from 'react'
import { AxesHelper } from 'three'
import { Canvas, useLoader } from '@react-three/fiber'
import { Environment, OrbitControls, Stats } from '@react-three/drei'
import ErrorBoundary from 'antd/es/alert/ErrorBoundary'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'

function MyMesh () {
const src = '/obj/demo2.obj'
const object = useLoader(OBJLoader, src)
console.log(object)
return (
<primitive object={object} />
)
}

export default function Demo () {
const statRef = useRef(null)
return (
<div ref={statRef}>
<Stats showPanel={0} parent={statRef} style={{ top: 'auto', bottom: 0 }} />
<ErrorBoundary>
<Canvas style={{ height: 800 }} camera={{ fov: 75, near: 0.1, far: 1000, position: [2, 1, 2] }}>
<Suspense fallback={null}>
<directionalLight color={'#fff'} intensity={1} position={[-3, 5, 5]} />
<primitive object={new AxesHelper(100)} />
<MyMesh />
<OrbitControls makeDefault />
<color attach='background' args={['#aaa']} />
<Environment
background={false} preset={null} scene={undefined}
path={'/img/three/env/'}
files={['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png']}
/>
</Suspense>
</Canvas>
</ErrorBoundary>
</div>
)
}

如果不出意外,你的模型已经出来了,但是有 一半 的可能你只是一个灰白物体,没上色。因为obj格式的 材质 部分是有要求的。首先 obj 可能没直接包含 mtl 文件,或者 mtl 文件没有相对路径于 obj 文件(同一目录)。其次 mtl 中的图片定义,也没有相对路径于 obj 文件。 情况复杂且多,需要大家根据 Network 自我 debug。

Demo3 自动对焦

这个需求很多,网上解释也很多,但我是没看懂,也没见人真解决。在翻看 threejs-editor 时,发现了 auto-focus 的源码(editor/js/EditorControls.js:34 focus),然后进行改装,这边给个参考。过程:先计算物体边界,转化成盒子模型,然后倍数,算出合适的位置 + 角度,然后设置回去。

import React, { Suspense, useEffect, useState } from 'react'
import { Box3, Sphere, Vector3 } from 'three'
import { Canvas, useLoader } from '@react-three/fiber'
import { OrbitControls, PerspectiveCamera } from '@react-three/drei'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'

const OBJ_POS_000 = [0, 0, 0]
const CAM_POS_212 = [1, .5, 1]

function calcBound (object, x = 1.5) {
const box = new Box3().setFromObject(object)

const center = new Vector3()
let distance
let objPos2 = new Vector3()

if (box.isEmpty() === false) {
/** @var {Vector3} */
objPos2 = box.getCenter(center)
objPos2.negate()
distance = box.getBoundingSphere(new Sphere()).radius
} else {
center.setFromMatrixPosition(object.matrixWorld)
distance = 0.1
}

const delta = new Vector3()
delta.set(...CAM_POS_212)
delta.applyQuaternion(object.quaternion)
delta.multiplyScalar(distance * x)
const camPos2 = new Vector3()
camPos2.copy(objPos2).add(delta)

return [objPos2.toArray(), camPos2.toArray()]
}

function MyMesh () {
const [objPos, setObjPos] = useState(OBJ_POS_000)
const [camPos, setCamPos] = useState(CAM_POS_212)

const src = '/obj/demo2.obj'
const object = useLoader(OBJLoader, src)
useEffect(() => {
const [objPos2, camPos2] = calcBound(object, 2.5)
setObjPos(objPos2)
setCamPos(camPos2)
// eslint-disable-next-line
}, [object])

return (
<>
<primitive object={object} position={objPos} />
<PerspectiveCamera makeDefault fov={50} far={5000} near={.01} position={camPos} />
</>
)
}

export default function Demo () {
return (
<Canvas style={{ height: 800 }} camera={{ fov: 75, near: 0.1, far: 1000, position: [2, 1, 2] }}>
<Suspense fallback={null}>
<ambientLight intensity={0.1} />
<directionalLight color={'#fff'} intensity={1} position={[-3, 5, 5]} />
<MyMesh />
<OrbitControls makeDefault />
<color attach='background' args={['#aaa']} />
</Suspense>
</Canvas>
)
}

进阶部分

前面的 demo 都给了完整可运行代码,后面部分由于需要配套的基础代码太多,只能给 关键部分。大家自行整合,实在不行,留言询问 up。

编辑物体位置、旋转、缩放

这个 demo 来自网上,但是缺少保存的过程,这边给个保存参考

// 保存逻辑参考

const { gl, scene, camera } = useThree()
// 前提是要有 uuid
const obj = scene.getObjectByName(uuid)
const partObj = packRef.current.getObjByUuid(uuid)
const position = partObj.position.toArray()
const rotation = partObj.rotation.toVector3().toArray()
console.log(partObj, position, rotation)
// 然后自己定义数据结构,入库
import { Suspense, useState } from 'react'
import { Canvas, useThree } from '@react-three/fiber'
import { ContactShadows, OrbitControls, TransformControls, useCursor, useGLTF } from '@react-three/drei'
import { proxy, useSnapshot } from 'valtio'

// Reactive state model, using Valtio ...
const modes = ['translate', 'rotate', 'scale']
const state = proxy({ current: null, mode: 0 })

function Model ({ name, ...props }) {
// Ties this component to the state model
const snap = useSnapshot(state)
// Fetching the GLTF, nodes is a collection of all the meshes
// It's cached/memoized, it only gets loaded and parsed once
const { nodes } = useGLTF('https://utihp1.csb.app/compressed.glb')
// Feed hover state into useCursor, which sets document.body.style.cursor to pointer|auto
const [hovered, setHovered] = useState(false)
useCursor(hovered)
return (
<mesh
// Click sets the mesh as the new target
onClick={(e) => {
e.stopPropagation()
state.current = name
}}
// If a click happened but this mesh wasn't hit we null out the target,
// This works because missed pointers fire before the actual hits
onPointerMissed={(e) => {if (e.type === 'click') state.current = null}}
// Right click cycles through the transform modes
onContextMenu={(e) => {
if (snap.current === name) {
e.stopPropagation()
state.mode = (snap.mode + 1) % modes.length
}
}}
onPointerOver={(e) => {
e.stopPropagation()
setHovered(true)
}}
onPointerOut={() => setHovered(false)}
name={name}
geometry={nodes[name].geometry}
material={nodes[name].material}
material-color={snap.current === name ? '#ff6080' : 'white'}
{...props}
dispose={null}
/>
)
}

function Controls () {
// Get notified on changes to state
const snap = useSnapshot(state)
const scene = useThree((state) => state.scene)
return (
<>
{/* As of drei@7.13 transform-controls can refer to the target by children, or the object prop */}
{snap.current && <TransformControls object={scene.getObjectByName(snap.current)} mode={modes[snap.mode]} />}
{/* makeDefault makes the controls known to r3f, now transform-controls can auto-disable them when active */}
<OrbitControls makeDefault minPolarAngle={0} maxPolarAngle={Math.PI / 1.75} />
</>
)
}

export default function Home () {
return (
<Canvas camera={{ position: [0, -10, 80], fov: 50 }} dpr={[1, 2]}>
<pointLight position={[100, 100, 100]} intensity={0.8} />
<hemisphereLight color='#ffffff' groundColor='#b9b9b9' position={[-7, 25, 13]} intensity={0.85} />
<Suspense fallback={null}>
<group position={[0, 10, 0]}>
<Model name='Curly' position={[1, -11, -20]} rotation={[2, 0, -0]} />
<Model name='DNA' position={[20, 0, -17]} rotation={[1, 1, -2]} />
<Model name='Headphones' position={[20, 2, 4]} rotation={[1, 0, -1]} />
<Model name='Notebook' position={[-21, -15, -13]} rotation={[2, 0, 1]} />
<Model name='Rocket003' position={[18, 15, -25]} rotation={[1, 1, 0]} />
<Model name='Roundcube001' position={[-25, -4, 5]} rotation={[1, 0, 0]} scale={0.5} />
<Model name='Table' position={[1, -4, -28]} rotation={[1, 0, -1]} scale={0.5} />
<Model name='VR_Headset' position={[7, -15, 28]} rotation={[1, 0, -1]} scale={5} />
<Model name='Zeppelin' position={[-20, 10, 10]} rotation={[3, -1, 3]} scale={0.005} />
<ContactShadows rotation-x={Math.PI / 2} position={[0, -35, 0]} opacity={0.25} width={200} height={200} blur={1} far={50} />
</group>
</Suspense>
<Controls />
</Canvas>
)
}

截图拍照

当需要预览图时,生成出来的会更方便。 两种方式提供,第一种直接截屏当前状态,第二种新摄像头

const { gl, scene, camera } = useThree()

function doPhoto1() {
gl.render(scene, camera)
return gl.domElement.toDataURL()
}
function doPhotot2() {
const camera2 = new THREE.PerspectiveCamera(75, 1, 0.01, 1000)
camera2.copy(camera)
camera2.position.set(.6, .6, .6)
gl.render(scene, camera2)
return gl.domElement.toDataURL('image/jpeg', .5)
}

如何改色

最复杂的留到最后,前面的部分基本都是应用层,只需要对 api 足够了解就能做出很多案例。这部分将需要 自定义 Class,改写官方实现。这里有两种方式,一种是改写加载类,一种是提取后自己组装

还记得 const object = useLoader(OBJLoader, src) 吗
OBJLoader 是官方的类 examples/jsm/loaders/OBJLoader.js
其中parse 方法 this.materials.create( sourceMaterial.name ) 就是关键
所以我们可以定义一个 MyOBJLoader 然后改写 parse
// 另一种方式,维护一个 mapping 表格,然后使用 mesh
export function useMcc (metaTable) {
useEffect(async () => {
for (const unitOne of metaTable) {
const { objName, mtlName, mtlId, mtlUdfEnable, mtlUdfUrl } = unitOne
// const name = Math.random().toString(36).slice(-6)
const name = `${objName}-${mtlName}`
if (mtlId) {
mccObjects[name] = await loadByMid(mtlId, mtlUdfEnable, mtlUdfUrl)
} else {
mccObjects[name] = new THREE.MeshStandardMaterial({ color: str2rgb(name) })
}
}
setMccObjects({ ...mccObjects })
}, [metaTable])

return { mccObjects }
}
export function useMetaTable (mccObjects, object) {
const [tb, set] = useState([])

useEffect(() => {
if (!mccObjects) return
// console.log('mccObjects', mccObjects)

const ret = []
for (let mesh of object.children) {
ret.push({
uuid: mesh.uuid,
geometry: mesh.geometry,
material: mesh.material.length
? mesh.material.map(v => get(mccObjects, mesh.name, v.name))
: get(mccObjects, mesh.name, mesh.material.name),
})
}
set(ret)
}, [mccObjects])

function get (arr, objName, mtlName) {
const name = `${objName}-${mtlName}`
return arr[name]
}

return tb
}

const { mccObjects } = useMcc(meta)
const metaTable = useMetaTable(mccObjects, object)
<group position={objPos}>
{metaTable.map(v => (
<mesh key={v.uuid} geometry={v.geometry} material={v.material} />
))}
</group>

总结

这些 demo 覆盖了很多场景,有疑问欢迎交流。所有代码随意使用,转发请带原文。

· 5 min read

https://blog.csdn.net/wolanx/article/details/122857729

Intro

免费个人博客 的教程非常的 ,但大多面向 纯小白,反而对有一定基础的同学显得 落后 + 啰嗦,本文整合目前已知最好的方式,最快捷优雅的搞定一个博客,并给出 常见错误

知识点

  • hugo 的安装及使用不是本文重点,其实同理 hexo
  • git github 默认已掌握
  • github page 生成可访问的页面的 灵魂 所在
  • github action 自动生成上一步

完整链路解析

  • git commit 提交
  • 触发 github action .github/workflows/my-pipeline.yml 并满足设置中的 on 条件
  • 触发 pipeline 中的 steps
    • checkout:相当与 git clone,并且后续操作具有 github 完整权限,可以通过 permissions 设置
    • setup hugo:准备构建要求,安装对应版本,注意是否需要 extended
    • build:构建出静态文件,并输出到 public 文件夹
    • deploy:该插件来自 插件市场
      • 自动创建分支 gh-pages
      • 自动 copy public 到新分支
      • 自动提交
      • 自动生成 CNAME 文件,根据 cname 设置,想要 自定义域名 的注意这里了
  • 打开 https://github.com/{你的名字}/{你的仓库}/settings/pages(后续步骤只需要一次)
    • Source 选择 gh-pages ,文件夹: 默认 / (root) ,并 save
    • 注意上方提示 Your site is ready to be published at https://xxx.github.io/xxx/
    • 将域名部分做 解析
    • Custom domain 设置 自己的域名
    • Enforce HTTPS 点一下,然后等一会

步骤1:hugo github

步骤1.1: 创建 仓库 & 初始化 hugo

hugo github 的基本操作不是本文重点,忽略

步骤1.2: 创建 .gitmodules 文件

hugo 的 主题 themes 是通过 git 的 sub modules 实现,而 github 上 git 会自动根据 .gitmodules clone 子项目。没有 .gitmodules 文件会导致构建失败。 比如我的主题使用的是 hugo-book(推荐),那么配置如下

[submodule "themes/hugo-book"]
path = themes/hugo-book
url = https://github.com/alex-shpak/hugo-book

步骤2:github action 自动生成

官方默认的 Jekyll 其实是会根据分支 自动 构建发布的,但如果自己魔改使用 hugo | hexo 这类软件以后就不会自动,需要使用 action 功能,而 action 其实就是 github 的 pipeline版本,使用只需要一个文件 .github/workflows/my-pipeline.yml。其中可能需要改的:

  • main 分支 根据实际情况改一下
  • cname 最后一行 如果要使用自定义域名功能

步骤2.1: 创建 .github/workflows/my-pipeline.yml

name: my-pipeline

on:
push:
tags:
- '*'
branches:
- main

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build:
runs-on: ubuntu-latest
# permissions:
# contents: read
# packages: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

steps:
- name: checkout
uses: actions/checkout@v2
with:
submodules: true # Fetch Hugo themes (true OR recursive)
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod

- name: setup hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.92.0'
extended: true

- name: build
run: hugo --minify

- name: deploy
uses: peaceiris/actions-gh-pages@v3
if: ${{ github.ref == 'refs/heads/main' }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
# cname: wolanx.com # 重点 !!!

步骤3:github page 页面生成

打开 https://github.com/{你的名字}/{你的仓库}/settings/pages , 设置

在这里插入图片描述

  • 没有 pages 功能:只有公开项目才有,私有 项目不支持
  • gh-pages 分支不存在:没有成功触发 action,成功触发,会自动生成分支 gh-pages,并将静态资源保存在 gh-pages 上
  • cname 一直被重置:.github/workflows/my-pipeline.yml 最后一行没有设置好
  • 域名设置没用:需要先做 域名解析,如阿里云,参考如下,记录值 是 github page 页面上给出的值的域名部分,!!!域名部分!!!域名部分

在这里插入图片描述

源码参考

总结

2022年了,很多原本复杂的东西,都已经变得非常的容易获得,但网上的文章常年不升级。根据本文方便你更好的白嫖到一个免费博客,如果需要支持自定义域名,阿里云上购买也不到100RMB。

· 2 min read

https://blog.csdn.net/wolanx/article/details/122733747

Intro

现在很多主流日志系统推崇 logfmt 格式,但是 python 中配套的不多,这边给个参考

日志大概长这样

# log.info("haha")
time="2022-01-28T17:00:52+0800" type=default level=info method="a.py:82" msg="haha"
# log.warning("no access")
time="2022-01-28T17:00:52+0800" type=default level=warning method="a.py:83" msg="no access"

实现过程

  • PiiLogger 继承 logging.Logger
    • 绑定自定义的 formatter
    • 清空原有 handler 否则会重复输出
    • 把 formatter 注册给 handler
    • hook 外层 变量 (如:每条log带上web请求的uuid)
  • PiiLoggerFormatter 继承 logging.Formatter

外层变量的使用

def getUUid():
v = None
if has_request_context(): # 判断 flask web 的生命周期下
v = Pii.app.get("uuid", "") # 根据自己业务写

return {"uuid": v}

log = PiiLogger.manager.getLogger("default")
log.withFormatter(getUUid)
log.info("haha")
# time="2022-01-29T10:38:21+0800" type=default level=info method="views.request_after" uuid="860ea870-80ac-11ec-a366-1eaadecc49e8" msg="haha"

完整代码

import logging
import numbers
from json.encoder import JSONEncoder
from typing import Any


class PiiLogger(logging.Logger):
def __init__(self, name: str, level=logging.NOTSET) -> None:
super().__init__(name, level)

self.setLevel(logging.INFO)
self.root.handlers.clear()

ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
self.formatter = PiiLoggerFormatter(
fmt='time="%(asctime)s" type=%(name)s level=%(levelname)s method="%(method)s"',
datefmt="%Y-%m-%dT%H:%M:%S%z",
)
ch.setFormatter(self.formatter)

self.addHandler(ch)

def withFields(self, ret):
print(ret)
return self

def withFormatter(self, func):
self.formatter.setExt(func)


class PiiLoggerFormatter(logging.Formatter):
ext: Any = None

def format(self, record):
if record.funcName == "<module>":
method = f"{record.filename}:{record.lineno}"
else:
method = f"{record.module}.{record.funcName}"

record.__setattr__("method", method)
record.levelname = record.levelname.lower()
msg = JSONEncoder().encode(str(record.msg))

ret = super().format(record)

if self.ext:
mor = logfmt(self.ext())
if mor:
ret += " " + mor

return f"{ret} msg={msg}"

def setExt(self, func):
self.ext = func


def logfmt(extra):
outarr = []
for k, v in extra.items():
if v is None:
outarr.append("%s=" % k)
continue

if isinstance(v, bool):
v = "true" if v else "false"
elif isinstance(v, numbers.Number):
pass
else:
if isinstance(v, (dict, object)):
v = str(v)
v = '"%s"' % v.replace('"', '\\"')
outarr.append("%s=%s" % (k, v))
return " ".join(outarr)


PiiLogger.manager.setLoggerClass(PiiLogger)


if __name__ == "__main__":
# use
log = PiiLogger.manager.getLogger("default")
log.info("haha")
log.warning("no access")

· 4 min read

https://blog.csdn.net/wolanx/article/details/122828185

Intro

项目中遇到需要 导出统计报表 等业务时,通常需要 pdf 格式。python 中比较有名的就是 reportlab 。 这边通过几个小 demo 快速演示常用 api。所有功能点 源码 都在 使用场景

一句话了解:跟 css 差不多,就是不断地对每样东西设置 style,然后把 style 和内容绑定。

功能点

  • 生成
    • 文件: 先 SimpleDocTemplate('xxx.pdf'),然后 build
    • 流文件:先 io.BytesIO() 生成句柄,然后同理
  • 曲线图 LinePlot
  • 饼图 Pie
  • 文字 Paragraph
    • fontSize 字体大小 推荐 14
    • 加粗 <b>xxx</b> 使用的是 html 的方式,字体自动实现
    • firstLineIndent 首行缩进 推荐 2 * fontSize
    • leading 行间距 推荐 1.5 * fontSize
    • fontName 默认中文会变成 ■
      • 下载 .ttf 文件 至少2个 【常规】【加粗】
      • 注册字体 pdfmetrics.registerFont 【常规】请用原名,方便加粗的实现
      • 注册字体库 registerFontFamily("HanSans", normal="HanSans", bold="HanSans-Bold")

其他 api 自行摸索,但基本离不开 css 那种理念。官网并没有常规文档的那种 md 模式,而是完全写在了 pdf 里,玩家需要自己去 pdf 里像查字典一样去找。官方文档

预览

在这里插入图片描述

完整代码

import os

from reportlab.graphics.charts.lineplots import LinePlot
from reportlab.graphics.charts.piecharts import Pie
from reportlab.graphics.shapes import Drawing
from reportlab.lib import colors
from reportlab.lib.styles import ParagraphStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.pdfmetrics import registerFontFamily
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import Paragraph

home = os.path.expanduser("~")

try:
pdfmetrics.registerFont(TTFont("HanSans", f"{home}/.fonts/SourceHanSansCN-Normal.ttf"))
pdfmetrics.registerFont(TTFont("HanSans-Bold", f"{home}/.fonts/SourceHanSansCN-Bold.ttf"))
registerFontFamily("HanSans", normal="HanSans", bold="HanSans-Bold")
FONT_NAME = "HanSans"
except:
FONT_NAME = "Helvetica"


class MyCSS:
h3 = ParagraphStyle(name="h3", fontName=FONT_NAME, fontSize=14, leading=21, alignment=1)
p = ParagraphStyle(name="p", fontName=FONT_NAME, fontSize=12, leading=18, firstLineIndent=24)


class PiiPdf:
@classmethod
def doH3(cls, text: str):
return Paragraph(text, MyCSS.h3)

@classmethod
def doP(cls, text: str):
return Paragraph(text, MyCSS.p)

@classmethod
def doLine(cls):
drawing = Drawing(500, 220)
line = LinePlot()
line.x = 50
line.y = 50
line.height = 125
line.width = 300
line.lines[0].strokeColor = colors.blue
line.lines[1].strokeColor = colors.red
line.lines[2].strokeColor = colors.green
line.data = [((0, 50), (100, 100), (200, 200), (250, 210), (300, 300), (400, 800))]

drawing.add(line)
return drawing

@classmethod
def doChart(cls, data):
drawing = Drawing(width=500, height=200)
pie = Pie()
pie.x = 150
pie.y = 65
pie.sideLabels = False
pie.labels = [letter for letter in "abcdefg"]
pie.data = data # list(range(15, 105, 15))
pie.slices.strokeWidth = 0.5

drawing.add(pie)
return drawing

使用场景1:生成文件

doc = SimpleDocTemplate("Hello.pdf")

p = PiiPdf()
doc.build([
p.doH3("<b>水泵能源消耗简报</b>"),
p.doH3("<b>2021.12.1 ~ 2021.12.31</b>"),
p.doP("该月接入能耗管理系统水泵系统 xx 套,水泵 x 台。"),
p.doP("本月最大总功率 xx kW,环比上月增加 xx %,平均功率 xx kW;环比上月增加 xx %。"),
p.doP("功率消耗趋势图:"),
p.doLine(),
p.doP("本月总能耗 xxx kWh,环比上月增加 xx %。"),
p.doP("分水泵能耗统计:"),
p.doChart(list(range(15, 105, 20))),
p.doP("其中能耗最高的水泵为:xxx, 环比上月增加 xxx kWh,xx %。"),
])

使用场景2:web(flask)

@Controller.get("/api/pdf")
def api_hub_energy_pdf():
buffer = io.BytesIO() # 重点 起一个 io
doc = SimpleDocTemplate(buffer)

p = PiiPdf()
doc.build([
p.doH3("<b>2021.12.1 ~ 2021.12.31</b>"),
])

buffer.seek(0)
return Response( # io 形式返回
buffer,
mimetype="application/pdf",
headers={"Content-disposition": "inline; filename=test.pdf"},
)

· 8 min read

From csdn

欢迎使用Markdown编辑器

你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。 1: mermaid语法说明

功能快捷键

撤销:Ctrl/Command + Z 重做:Ctrl/Command + Y 加粗:Ctrl/Command + B 斜体:Ctrl/Command + I 标题:Ctrl/Command + Shift + H 无序列表:Ctrl/Command + Shift + U 有序列表:Ctrl/Command + Shift + O 检查列表:Ctrl/Command + Shift + C 插入代码:Ctrl/Command + Shift + K 插入链接:Ctrl/Command + Shift + L 插入图片:Ctrl/Command + Shift + G 查找:Ctrl/Command + F 替换:Ctrl/Command + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。 输入2次#,并按下space后,将生成2级标题。 以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

==标记文本==

删除文本

引用文本

H~2~O is是液体。

2^10^ 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: ![Alt](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9hdmF0YXIuY3Nkbi5uZXQvNy83L0IvMV9yYWxmX2h4MTYzY29tLmpwZw =30x30)

居中的图片: Alt

居中并且带尺寸的图片: ![Alt](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9hdmF0YXIuY3Nkbi5uZXQvNy83L0IvMV9yYWxmX2h4MTYzY29tLmpwZw#pic_center =30x30)

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的: 项目 | Value -------- | ----- 电脑 | $1600 手机 | $12 导管 | $1

设定内容居中、居左、居右

使用:---------:居中 使用:----------居左 使用----------:居右 | 第一列 | 第二列 | 第三列 | |:-----------:| -------------:|:-------------| | 第一列文本居中 | 第二列文本居右 | 第三列文本居左 |

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如: | TYPE |ASCII |HTML |----------------|-------------------------------|-----------------------------| |Single backticks|'Isn't this fun?' |'Isn't this fun?' | |Quotes |"Isn't this fun?" |"Isn't this fun?" | |Dashes |-- is en-dash, --- is em-dash|-- is en-dash, --- is em-dash|

创建一个自定义列表

Markdown : Text-to-HTML conversion tool

Authors : John : Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML。

*[HTML]: 超文本标记语言

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 $\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N$ 是通过欧拉积分

$$ \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. $$

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram functionality to mermaid
section 现有任务
已完成 :done, des1, 2014-01-06,2014-01-08
进行中 :active, des2, 2014-01-09, 3d
计划一 : des3, after des2, 5d
计划二 : des4, after des3, 5d
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

sequenceDiagram
张三 ->> 李四: 你好!李四, 最近怎么样?
李四-->>王五: 你最近怎么样,王五?
李四--x 张三: 我很好,谢谢!
李四-x 王五: 我很好,谢谢!
Note right of 王五: 李四想了很长时间, 文字太长了<br/>不适合放在一行.

李四-->>张三: 打量着王五...
张三->>王五: 很好... 王五, 你怎么样?

这将产生一个流程图。:

graph LR
A[长方形] -- 链接 --> B((圆))
A --> C(圆角长方形)
B --> D{菱形}
C --> D
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

flowchat
st=>start: 开始
e=>end: 结束
op=>operation: 我的操作
cond=>condition: 确认?

st->op->cond
cond(yes)->e
cond(no)->op
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入, 继续你的创作。


  1. 注脚的解释