🍁 前言

最近阅读论文,在做笔记的时候总是要手动输入一些latex公式,非常耗时。我使用Hapigo的Latex 公式识别,感觉还挺好用,但是缺陷是只有30次免费额度,于是在网上搜索了一下,发现可以通过本地部署Latex OCR来无限制识别latex公式。下面是我部署latex OCR的过程以及我自己总结的一些优化技巧。

🌿 部署

在 M1 上安装 LaTeX-OCR 识别工具
珠玉在前,就不班门弄斧了,需要注意的是这篇帖子的第3步的路径需要修改为你本机电脑的路径。

sudo cp -r /opt/homebrew/Cellar/pyqt@5/5.15.7_2/lib/python3.9/site-packages/* /Users/rey/miniconda3/lib/python3.9/site-packages/

主要就是修改/5.15.7_2python3.9rey,通过按Tap键的方式可以快速补全。
同时为了防止链接失效,我也手动将作者的步骤粘贴如下:

  1. pip install "pix2tex[gui]"
  2. brew install pyqt@5
  3. sudo cp -r /opt/homebrew/Cellar/pyqt@5/5.15.7_2/lib/python3.9/site-packages/* /Users/rey/miniconda3/lib/python3.9/site-packages/
  4. pip install pynput screeninfo
  5. conda install pytorch torchvision
    在这里插入图片描述

除了图片中提到的在命令行中输入python -m pix2tex latexocr的使用方法,还可以使用latex OCR的GUI界面,只需要在终端输入latexocr或者pix2tex_gui,稍等片刻(打开30秒),就会打开相应的GUI界面。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

🌱 优化

主要优化了两个点:

  1. 将代码打包成了Mac下的app
  2. 注册了全局快捷键,可以在程序后台运行时按下快捷键直接调用OCR识别公式

打包成app

  1. 打开Mac的“自动操作”App
    在这里插入图片描述
  2. 搜索运行脚本,并双击“运行Shell脚本”,此时右边会出现对应的流程项

在这里插入图片描述
选择Shell类型,这里可以在终端输入echo $SHELL来查看你当前使用的shell是什么类型,我这里是/bin/zsh
在这里插入图片描述

  1. 编写脚本内容,首先在终端输入where latexocr,会输出可执行文件的绝对路径,学过计算机组成原理都知道,在unix系统下,在终端中直接输入可执行文件的绝对路径就能够直接运行这个可执行文件,其原理就是shell解释器会去找到这个路径对应的可执行文件并运行之。综上,这个可执行文件路径就是我们要编写的脚本命令.
    在这里插入图片描述
    在这里插入图片描述
  2. 测试运行,点击右上角的运行,稍等片刻,就会弹出GUI窗口,测试成功
    在这里插入图片描述

在这里插入图片描述

  1. 打包成app
    在这里插入图片描述
    在这里插入图片描述

  2. 更改图标,默认的自动化打包的app图标丑的一批,左边是我优化后的图标,看起来舒服多了,在搜索引擎上搜索关键词“OCR 图标”可以找到类似的图标,大家可以自己挑选
    在这里插入图片描述
    更换方式如下

  1. 复制你的图标图片到剪切板
  2. 在"应用程序"中找到刚刚打包好的app,选中,按下command + I,显示简介
  3. 选中左上角的图标
    在这里插入图片描述
  4. 按下command + V粘贴你刚才复制的图标图片,即可替换成功

注册全局快捷键

在使用的时候,有个很不方便的地方是,你必须打开latex ocr的窗口,然后按下快捷键才能够调用OCR截图识别公式,就很麻烦。于是我在想能不能让程序在后台运行的时候,自动监听快捷键,在无需显示打开窗口的情况下就能直接调用OCR截图识别,经过我的尝试,发现通过修改源代码的方式,在代码中使用pynut库可以达到预想的效果。

/opt/miniconda3/lib/python3.12/site-packages/pix2tex路径下(大致路径是这样,请根据本机的具体情况微调)找到gui.py这个文件,打开并编辑之。(温馨提示:修改前记得先备份哦)

我主要是修改了这几个地方:

  1. 将默认的识别格式LaTeX-$修改成了Raw,这样识别的结果前后就没有$
  2. 增加了系统托盘
    在这里插入图片描述
  3. 将快捷键设置成了option + ctrl,这是因为输入option + 字母组合,pynut会将其识别成特殊字符,而不是组合键,比如按下option + z,就会识别成Ω,所以这里选择了不会产生特殊字符的组合键option + ctrl
  4. 增加了后台监听快捷键的功能,app在后台运行时也能够监听到快捷键

读者感兴趣的话,也可以使用文本对比工具(比如Beyond Compare)比较我修改的代码和原有代码的区别

最后在这里直接贴一下源代码,需要源代码文件也可以通过网盘下载。

网盘链接

源代码(修改版)

from shutil import which
import io
import subprocess
import sys
import os
import re
import tempfile
import threading
from PyQt6 import QtCore, QtGui
from PyQt6.QtCore import Qt, pyqtSlot, pyqtSignal, QThread, QTimer, QEvent
from PyQt6.QtGui import QGuiApplication
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import QMainWindow, QApplication, QMessageBox, QVBoxLayout, QWidget, \QPushButton, QTextEdit, QFormLayout, QHBoxLayout, QDoubleSpinBox, QLabel, QRadioButton, \QSystemTrayIcon, QMenu
from pynput.mouse import Controller
from pynput import keyboard
from pynput.keyboard import Key, Listenerfrom PIL import ImageGrab, Image, ImageEnhance
import numpy as np
from screeninfo import get_monitors
from pix2tex import cli
from pix2tex.utils import in_model_path
from latex2sympy2 import latex2sympyimport pix2tex.resources.resourcesACCEPTED_IMAGE_SUFFIX = ['png', 'jpg', 'jpeg']def to_sympy(latex):normalized = re.sub(r'operatorname\*{(\w+)}', '\g<1>', latex)sympy_expr = latex2sympy(f'${normalized}$')return sympy_exprclass WebView(QWebEngineView):def __init__(self, app) -> None:super().__init__()self.setAcceptDrops(True)self._app = appdef dragEnterEvent(self, event):if event.mimeData().urls():event.accept()else:event.ignore()def dropEvent(self, event):urls = event.mimeData().urls()self._app.returnFromMimeData(urls)class App(QMainWindow):isProcessing = FalseglobalHotkeyPressed = pyqtSignal()  # 添加全局热键信号def __init__(self, args=None):super().__init__()self.args = argsself.model = cli.LatexOCR(self.args)self.initUI()self.snipWidget = SnipWidget(self)# 初始化系统托盘self.initTray()# 连接全局热键信号self.globalHotkeyPressed.connect(self.onClick)# 启动全局热键监听self.hotkey_thread = threading.Thread(target=self.start_global_hotkey_listener, daemon=True)self.hotkey_thread.start()self.show()def initTray(self):"""初始化系统托盘"""self.tray = QSystemTrayIcon(self)self.tray.setIcon(QtGui.QIcon(':/icons/icon.svg'))# 创建托盘菜单tray_menu = QMenu()self.show_action = tray_menu.addAction("显示窗口")self.show_action.triggered.connect(self.showNormal)quit_action = tray_menu.addAction("退出")quit_action.triggered.connect(QApplication.quit)self.tray.setContextMenu(tray_menu)self.tray.show()def start_global_hotkey_listener(self):"""启动全局热键监听"""# 创建按键状态集合keys_pressed = set()def on_press(key):try:# 检测 Option/Alt 键if key == Key.alt or key == Key.alt_l or key == Key.alt_r:keys_pressed.add('alt')# 检测 Ctrl 键elif key == Key.ctrl or key == Key.ctrl_l or key == Key.ctrl_r:keys_pressed.add('ctrl')# 检查是否同时按下了 Alt 和 Ctrlif 'alt' in keys_pressed and 'ctrl' in keys_pressed:# 确保在主线程中发出信号QtCore.QMetaObject.invokeMethod(self, "globalHotkeyPressed", QtCore.Qt.ConnectionType.QueuedConnection)# 清空按键集合,避免连续触发keys_pressed.clear()except Exception as e:print(f"热键监听错误: {e}")def on_release(key):try:# 释放按键时从集合中移除if key == Key.alt or key == Key.alt_l or key == Key.alt_r:keys_pressed.discard('alt')elif key == Key.ctrl or key == Key.ctrl_l or key == Key.ctrl_r:keys_pressed.discard('ctrl')except Exception as e:print(f"热键监听错误 (释放): {e}")with Listener(on_press=on_press, on_release=on_release) as listener:listener.join()def closeEvent(self, event):"""窗口关闭事件处理"""if self.tray.isVisible():self.hide()event.ignore()def initUI(self):self.setWindowTitle("LaTeX OCR")QApplication.setWindowIcon(QtGui.QIcon(':/icons/icon.svg'))self.left = 300self.top = 300self.width = 500self.height = 300self.setGeometry(self.left, self.top, self.width, self.height)self.format_type = 'Raw' # 秋窗修改了初始化格式self.raw_prediction = ''# Create LaTeX displayself.webView = WebView(self)self.webView.setHtml("")self.webView.setMinimumHeight(80)# Create textboxself.textbox = QTextEdit(self)# self.textbox.textChanged.connect(self.displayPrediction)self.textbox.textChanged.connect(self.onTextboxChange)self.textbox.setMinimumHeight(40)self.format_textbox = QTextEdit(self)# self.textbox.textChanged.connect(self.displayPrediction)self.format_textbox.textChanged.connect(self.onFormatTextboxChange)self.format_textbox.setMinimumHeight(40)# format typesformat_types = QHBoxLayout()self.format_label = QLabel('Format:', self)self.format_type0 = QRadioButton('Raw', self)self.format_type0.toggled.connect(self.onFormatChange)self.format_type1 = QRadioButton('LaTeX-$', self)self.format_type0.setChecked(True) # 秋窗修改此处,以默认选择Raw格式self.format_type1.toggled.connect(self.onFormatChange)self.format_type2 = QRadioButton('LaTeX-$$', self)self.format_type2.toggled.connect(self.onFormatChange)self.format_type3 = QRadioButton('Sympy', self)self.format_type3.toggled.connect(self.onFormatChange)format_types.addWidget(self.format_label)format_types.addWidget(self.format_type0)format_types.addWidget(self.format_type1)format_types.addWidget(self.format_type2)format_types.addWidget(self.format_type3)# error outputself.error = QTextEdit(self)self.error.setReadOnly(True)self.error.setTextColor(Qt.GlobalColor.red)self.error.setMinimumHeight(12)# Create temperature text inputself.tempField = QDoubleSpinBox(self)self.tempField.setValue(self.args.temperature)self.tempField.setRange(0, 1)self.tempField.setSingleStep(0.1)# Create snip buttonif sys.platform == "darwin":self.snipButton = QPushButton('Snip [Option+Ctrl]', self)  # 修改按钮文本self.snipButton.clicked.connect(self.onClick)else:self.snipButton = QPushButton('Snip [Alt+Ctrl]', self)  # 修改按钮文本self.snipButton.clicked.connect(self.onClick)self.shortcut = QtGui.QShortcut(QtGui.QKeySequence('Ctrl+Alt+Z'), self)  # 修改快捷键self.shortcut.activated.connect(self.onClick)# Create retry buttonself.retryButton = QPushButton('Retry', self)self.retryButton.setEnabled(False)self.retryButton.clicked.connect(self.returnSnip)# Create layoutcentralWidget = QWidget()centralWidget.setMinimumWidth(200)self.setCentralWidget(centralWidget)lay = QVBoxLayout(centralWidget)lay.addWidget(self.webView, stretch=4)lay.addWidget(self.textbox, stretch=2)lay.addLayout(format_types)lay.addWidget(self.format_textbox, stretch=2)lay.addWidget(self.error, stretch=1)buttons = QHBoxLayout()buttons.addWidget(self.snipButton)buttons.addWidget(self.retryButton)lay.addLayout(buttons)settings = QFormLayout()settings.addRow('Temperature:', self.tempField)lay.addLayout(settings)self.installEventFilter(self)def toggleProcessing(self, value=None):if value is None:self.isProcessing = not self.isProcessingelse:self.isProcessing = valueif self.isProcessing:text = 'Interrupt'func = self.interruptelse:if sys.platform == "darwin":text = 'Snip [Option+Ctrl]'  # 修改按钮文本else:text = 'Snip [Alt+Ctrl]'  # 修改按钮文本func = self.onClickself.retryButton.setEnabled(True)self.shortcut.setEnabled(not self.isProcessing)self.snipButton.setText(text)self.snipButton.clicked.disconnect()self.snipButton.clicked.connect(func)self.displayPrediction()def eventFilter(self, obj, event):if event.type() == QEvent.Type.KeyRelease:if event.key() == Qt.Key.Key_V and event.modifiers() == Qt.KeyboardModifier.ControlModifier:clipboard = QApplication.clipboard()img = clipboard.image()if not img.isNull():self.returnSnip(Image.fromqimage(img))else:self.returnFromMimeData(clipboard.mimeData().urls())return super().eventFilter(obj, event)@pyqtSlot()def onClick(self):"""点击截图按钮或快捷键时调用"""# 确保窗口可见if self.isHidden():self.showNormal()self.activateWindow()self.raise_()self.close()if os.environ.get('SCREENSHOT_TOOL') == "gnome-screenshot":self.snip_using_gnome_screenshot()elif os.environ.get('SCREENSHOT_TOOL') == "spectacle":self.snip_using_spectacle()elif os.environ.get('SCREENSHOT_TOOL') == "grim":self.snip_using_grim()elif os.environ.get('SCREENSHOT_TOOL') == "pil":self.snipWidget.snip()elif which('gnome-screenshot'):self.snip_using_gnome_screenshot()elif which('grim') and which('slurp'):self.snip_using_grim()else:self.snipWidget.snip()@pyqtSlot()def interrupt(self):if hasattr(self, 'thread'):self.thread.terminate()self.thread.wait()self.toggleProcessing(False)def snip_using_gnome_screenshot(self):try:with tempfile.NamedTemporaryFile() as tmp:subprocess.run(["gnome-screenshot", "--area", f"--file={tmp.name}"])# Use `tmp.name` instead of `tmp.file` due to compatability issues between Pillow and tempfileself.returnSnip(Image.open(tmp.name))except:print(f"Failed to load saved screenshot! Did you cancel the screenshot?")print("If you don't have gnome-screenshot installed, please install it.")self.returnSnip()def snip_using_spectacle(self):try:with tempfile.NamedTemporaryFile() as tmp:subprocess.run(["spectacle", "-r", "-b", "-n", "-o", f"{tmp.name}"])self.returnSnip(Image.open(tmp.name))except:print(f"Failed to load saved screenshot! Did you cancel the screenshot?")print("If you don't have spectacle installed, please install it.")self.returnSnip()def snip_using_grim(self):try:p = subprocess.run('slurp',check=True,capture_output=True,text=True)geometry = p.stdout.strip()p = subprocess.run(['grim', '-g', geometry, '-'],check=True,capture_output=True)self.returnSnip(Image.open(io.BytesIO(p.stdout)))except:print(f"Failed to load saved screenshot! Did you cancel the screenshot?")print("If you don't have slurp and grim installed, please install them.")self.returnSnip()def returnFromMimeData(self, urls):if not urls or not urls[0]:returnimage_url = urls[0]if image_url and image_url.scheme() == 'file' and image_url.fileName().split('.')[-1] in ACCEPTED_IMAGE_SUFFIX:image_path = image_url.toLocalFile()return self.returnSnip(Image.open(image_path))def returnSnip(self, img=None):self.toggleProcessing(True)self.retryButton.setEnabled(False)if img:width, height = img.sizeif width <= 0 or height <= 0:self.toggleProcessing(False)self.retryButton.setEnabled(True)self.show()returnif width < 100 or height < 100: # too small size will make OCR wrongscale_factor = max(100 / width, 100 / height)new_width = int(width * scale_factor)new_height = int(height * scale_factor)img = img.resize((new_width,new_height), Image.Resampling.LANCZOS)contrast = ImageEnhance.Contrast(img)img = contrast.enhance(1.5)sharpness = ImageEnhance.Sharpness(img)img = sharpness.enhance(1.5)self.show()try:self.model.args.temperature = self.tempField.value()if self.model.args.temperature == 0:self.model.args.temperature = 1e-8except:pass# Run the model in a separate threadself.thread = ModelThread(img=img, model=self.model)self.thread.finished.connect(self.returnPrediction)self.thread.finished.connect(self.thread.deleteLater)self.thread.start()def returnPrediction(self, result):self.toggleProcessing(False)success, prediction = result["success"], result["prediction"]if success:self.raw_prediction = predictionself.textbox.setText(prediction)self.format_textbox.setText(self.formatPrediction(prediction))self.displayPrediction(prediction)self.retryButton.setEnabled(True)else:self.webView.setHtml("")msg = QMessageBox()msg.setWindowTitle(" ")msg.setText("Prediction failed.")msg.exec()def onFormatChange(self):rb = self.sender()if rb.isChecked():self.format_type = rb.text()#self.format_textbox.setText(self.formatPrediction(self.raw_prediction)) # 秋窗修改了此处,因为把初始格式设置成了Raw,不注释这行会报错 def formatPrediction(self, prediction, format_type=None):self.error.setText("")prediction = prediction or self.format_textbox.toPlainText()raw = prediction.strip('$')if len(raw) == 0:return ''format_type = format_type or self.format_typeif format_type == "Raw":formatted = rawelif format_type == "LaTeX-$":formatted = f"${raw}$"elif format_type == "LaTeX-$$":formatted = f"$${raw}$$"elif format_type == "MathJax":formatted = rawelif format_type == "Sympy":try:formatted = str(to_sympy(raw))except Exception as e:print(e)formatted = rawself.error.setText("Failed to parse Sympy expr.")else:return rawreturn formatteddef onTextboxChange(self):text = self.textbox.toPlainText()new_raw_prediction = self.formatPrediction(text, "Raw")if new_raw_prediction != self.raw_prediction:self.raw_prediction = new_raw_predictionself.format_textbox.setText(self.formatPrediction(self.raw_prediction))self.displayPrediction()def onFormatTextboxChange(self):text = self.format_textbox.toPlainText()clipboard = QApplication.clipboard()clipboard.setText(text)def displayPrediction(self, prediction=None):if self.isProcessing:pageSource = """<center><img src="qrc:/icons/processing-icon-anim.svg" width="50", height="50"></center>"""else:if prediction is None:prediction = self.textbox.toPlainText().strip('$')pageSource = """<html><head><script id="MathJax-script" src="qrc:MathJax.js"></script><script>MathJax.Hub.Config({messageStyle: 'none',tex2jax: {preview: 'none'}});MathJax.Hub.Queue(function () {document.getElementById("equation").style.visibility = "";});</script></head> """ + """<body><div id="equation" style="font-size:1em; visibility:hidden">$${equation}$$</div></body></html>""".format(equation=prediction)self.webView.setHtml(pageSource)class ModelThread(QThread):finished = pyqtSignal(dict)def __init__(self, img, model):super().__init__()self.img = imgself.model = modeldef run(self):try:prediction = self.model(self.img)# replace <, > with \lt, \gt so it won't be interpreted as html codeprediction = prediction.replace('<', '\\lt ').replace('>', '\\gt ')self.finished.emit({"success": True, "prediction": prediction})except Exception as e:import tracebacktraceback.print_exc()self.finished.emit({"success": False, "prediction": None})class SnipWidget(QMainWindow):isSnipping = Falsedef __init__(self, parent):super().__init__()self.parent = parentmonitos = get_monitors()bboxes = np.array([[m.x, m.y, m.width, m.height] for m in monitos])x, y, _, _ = bboxes.min(0)w, h = bboxes[:, [0, 2]].sum(1).max(), bboxes[:, [1, 3]].sum(1).max()self.setGeometry(x, y, w-x, h-y)self.begin = QtCore.QPoint()self.end = QtCore.QPoint()self.mouse = Controller()# Create and start the timerself.factor = QGuiApplication.primaryScreen().devicePixelRatio()self.timer = QTimer(self)self.timer.timeout.connect(self.update_geometry_based_on_cursor_position)self.timer.start(500)def update_geometry_based_on_cursor_position(self):if not self.isSnipping:return# Update the geometry of the SnipWidget based on the current screenmouse_pos = QtGui.QCursor.pos()screen = QGuiApplication.screenAt(mouse_pos)if screen:self.factor = screen.devicePixelRatio()screen_geometry = screen.geometry()self.setGeometry(screen_geometry)def snip(self):self.isSnipping = Trueself.setWindowFlags(QtCore.Qt.WindowType.WindowStaysOnTopHint)QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.CrossCursor))self.show()def paintEvent(self, event):if self.isSnipping:brushColor = (0, 180, 255, 100)opacity = 0.3else:brushColor = (255, 255, 255, 0)opacity = 0self.setWindowOpacity(opacity)qp = QtGui.QPainter(self)qp.setPen(QtGui.QPen(QtGui.QColor('black'), 2))qp.setBrush(QtGui.QColor(*brushColor))qp.drawRect(QtCore.QRect(self.begin, self.end))def keyPressEvent(self, event):if event.key() == QtCore.Qt.Key.Key_Escape.value:QApplication.restoreOverrideCursor()self.close()self.parent.show()event.accept()def mousePressEvent(self, event):self.startPos = self.mouse.positionself.begin = event.pos()self.end = self.beginself.update()def mouseMoveEvent(self, event):self.end = event.pos()self.update()def mouseReleaseEvent(self, event):self.isSnipping = FalseQApplication.restoreOverrideCursor()startPos = self.startPosendPos = self.mouse.positionx1 = int(min(startPos[0], endPos[0]))y1 = int(min(startPos[1], endPos[1]))x2 = int(max(startPos[0], endPos[0]))y2 = int(max(startPos[1], endPos[1]))self.repaint()QApplication.processEvents()try:img = ImageGrab.grab(bbox=(x1, y1, x2, y2), all_screens=True)except Exception as e:if sys.platform == "darwin":img = ImageGrab.grab(bbox=(x1//self.factor, y1//self.factor,x2//self.factor, y2//self.factor), all_screens=True)else:raise eQApplication.processEvents()self.close()self.begin = QtCore.QPoint()self.end = QtCore.QPoint()self.parent.returnSnip(img)def main(arguments):with in_model_path():if os.name != 'nt':os.environ['QTWEBENGINE_DISABLE_SANDBOX'] = '1'app = QApplication(sys.argv)ex = App(arguments)sys.exit(app.exec())

最后是我在使用时遇到一个bug,就是我在按下option + ctrl调用OCR时,如果此时我不小心点击了鼠标或者触控板,导致截图失败,会出现如下报错:
在这里插入图片描述此时,这个透明背景的窗口由于高度超出了屏幕高度,并且不能通过向上拖动窗口使其下面遮掩的部分显示出来,因此也就无法将其关闭,我研究了一下,找到下面这个方法:

打开"活动监视器"APP,搜索Latex OCR,关闭进程

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/87239.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/87239.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/87239.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

128K 长文本处理实战:腾讯混元 + 云函数 SCF 构建 PDF 摘要生成器

一、背景 在数字化办公时代&#xff0c;PDF 文档因其格式稳定、兼容性强等特点&#xff0c;成为知识分享与文档存储的主要载体之一。但随着文档规模的增长&#xff0c;如何快速提取关键信息成为亟待解决的问题。尤其对于 128K 字符及以上的长文本 PDF&#xff0c;传统处理方法…

Elasticsearch 排序性能提升高达 900 倍

作者&#xff1a;来自 Elastic Benjamin Trent, Mayya Sharipova, Chenhui Wang 及 Libby Lin 了解我们如何通过更快的 float / half_float 排序和 integer 排序的延迟优化来加快 Elasticsearch 排序速度。 Elasticsearch 引入了大量新功能&#xff0c;帮助你为你的使用场景构建…

Nginx重定向协议冲突解决方案:The plain HTTP request was sent to HTTPS port

问题原因 ​​服务器运行在 HTTPS 模式&#xff0c;但代码却发出了 HTTP 重定向指令&#xff0c;两套协议对不上&#xff0c;浏览器直接报错。​​ 在Java中&#xff0c;常见于response.sendRedirect()方法的使用。该方法默认生成基于HTTP的绝对URL&#xff0c;即便原始请求是…

机器学习如何让智能推荐“更懂你”,助力转化率飞跃?

机器学习如何让智能推荐“更懂你”,助力转化率飞跃? 今天咱聊聊一个电商、内容平台、社交App都离不开的“秘密武器”——智能推荐系统,以及机器学习到底如何帮它提升转化率的。 说白了,转化率就是“点进去买单”的概率。智能推荐做得好,转化率能蹭蹭上涨;做不好,用户滑…

Ruby CGI Session

Ruby CGI Session 引言 CGI&#xff08;Common Gateway Interface&#xff09;是一种网络服务器与外部应用程序&#xff08;如脚本或程序&#xff09;进行通信的协议。在Ruby语言中&#xff0c;CGI被广泛用于创建动态网页。本文将深入探讨Ruby CGI Session的相关知识&#xf…

从零开始的云计算生活——第二十四天,重起航帆,初见MySQL数据库

一.故事剧情 接下来要进入到一条比较长的路——mysql数据库&#xff0c;之后会用一段时间来学习mySQL数据库的内容&#xff0c;今天先从基础开始介绍mysql数据库。 二.MySQL数据库概述 1.数据库概念 数据库(Database) 简称DB&#xff0c;按照一定格式存储数据的一些文件的…

ES文件管理器v4.4.3(ES文件浏览器)

前言 ES文件管理器&#xff08;也叫ES文件浏览器&#xff09;是一款手机上用来看和管理文件的工具。你可以用它像在电脑上一样&#xff0c;把文件整理进不同的文件夹&#xff0c;查找照片、文档、视频都很方便。它还能看到平时看不到的隐藏文件&#xff0c;帮你清理一些没用的…

leetcode:693. 交替位二进制数(数学相关算法题,python3解法)

难度&#xff1a;简单 给定一个正整数&#xff0c;检查它的二进制表示是否总是 0、1 交替出现&#xff1a;换句话说&#xff0c;就是二进制表示中相邻两位的数字永不相同。 示例 1&#xff1a; 输入&#xff1a;n 5 输出&#xff1a;true 解释&#xff1a;5 的二进制表示是&am…

GRU与LSTM之间的联系和区别

前面我们谈到RNN与LSTM之间的关系&#xff0c;而GRU也是循环神经网络中的一种模型&#xff0c;那么它与LSTM有什么区别呢&#xff1f; 接下来我来对GRU&#xff08;Gated Recurrent Unit&#xff09;模型进行一次深度解析&#xff0c;重点关注其内部结构、参数以及与LSTM的对比…

2025年数字信号、计算机通信与软件工程国际会议(DSCCSE 2025)

2025年数字信号、计算机通信与软件工程国际会议&#xff08;DSCCSE 2025&#xff09; 2025 International Conference on Digital Signal, Computer Communication, and Software Engineering 一、大会信息 会议简称&#xff1a;DSCCSE 2025 大会地点&#xff1a;中国北京 审稿…

北峰智能SDC混合组网通信方案,助力无网络场景高效作业

在自然灾害、公共安全事件或大规模活动应急响应中&#xff0c;专用无线对讲通信因其不受外部网络限制、免去通话费用、无需拨号便可实现即时语音调度的特点&#xff0c;展现出其不可替代的价值。尤其在许多无基础设施的地区&#xff0c;对智能化调度管理的需求并不亚于城市地区…

HarmonyOS应用开发高级认证知识点梳理 (二) 组件交互

以下是 HarmonyOS 应用开发中 ‌组件交互‌ 的核心知识点梳理&#xff08;高级认证备考重点&#xff09;&#xff0c;涵盖事件传递、状态管理、通信机制及生命周期协同&#xff1a; 一、事件处理机制 基础交互类型‌ (1)点击事件&#xff08;onClick&#xff09; 核心要点‌…

【SQL优化案例】索引创建不合理导致SQL消耗大量CPU资源

#隐式转换 第一章 适用环境 oracle 11glinux 6.9 第二章 Top SQL概况 下面列出我们发现的特定模块中Top SQL的相关情况&#xff1a; SQL_ID 模块 SQL类型 主要问题 fnc58puaqkd1n 无 select 索引创建不合理&#xff0c;导致全索引扫描&#xff0c;产生了大量逻辑读 …

autoas/as 工程的RTE静态消息总线实现与端口数据交换机制详解

0. 概述 autoas/as 工程的RTE&#xff08;Runtime Environment&#xff09;通过自动生成C代码&#xff0c;将各SWC&#xff08;软件组件&#xff09;之间的数据通信全部静态化、结构化&#xff0c;实现了类似“静态消息总线”的通信模型。所有端口的数据交换都必须经过RTE接口…

【机器学习第四期(Python)】LightGBM 方法原理详解

LightGBM 概述 一、LightGBM 简介二、LightGBM 原理详解⚙️ 核心原理&#x1f9e0; LightGBM 的主要特点 三、LightGBM 实现步骤&#xff08;Python&#xff09;&#x1f9ea; 可调参数推荐完整案例代码&#xff08;回归任务 可视化&#xff09; 参考 LightGBM 是由微软开源的…

时序数据库IoTDB监控指标采集与可视化指南

一、概述 本文以时序数据库IoTDB V1.0.1版本为例&#xff0c;介绍如何通过Prometheus采集Apache IoTDB的监控指标&#xff0c;并使用Grafana进行可视化。 二、Prometheus聚合运算符 Prometheus支持多种聚合运算符&#xff0c;用于在时间序列数据上进行聚合操作。以下是一些常…

React安装使用教程

一、React 简介 React 是由 Facebook 开发和维护的一个用于构建用户界面的 JavaScript 库&#xff0c;适用于构建复杂的单页应用&#xff08;SPA&#xff09;。它采用组件化、虚拟 DOM 和声明式编程等理念&#xff0c;已成为前端开发的主流选择。 二、React 安装方式 2.1 使用…

.NET MAUI跨平台串口通讯方案

文章目录 MAUI项目架构设计平台特定实现接口定义Windows平台实现Android平台实现 MAUI主界面实现依赖注入配置相关学习资源.NET MAUI开发移动端开发平台特定实现依赖注入与架构移动应用发布跨平台开发最佳实践性能优化测试与调试开源项目参考 MAUI项目架构设计 #mermaid-svg-OG…

BUUCTF在线评测-练习场-WebCTF习题[MRCTF2020]你传你[特殊字符]呢1-flag获取、解析

解题思路 打开靶场&#xff0c;左边是艾克&#xff0c;右边是诗人&#xff0c;下面有个文件上传按钮 结合题目&#xff0c;是一个文件上传漏洞&#xff0c;一键去世看源码可知是提交按钮&#xff0c;先上传个一句话木马.php试试 <?php eval($_POST[shell]); ?> 被过…

【容器】容器平台初探 - k8s整体架构

目录 K8s总揽 K8s主要组件 组件说明 一、Master组件 二、WokerNode组件 K8s是Kubernetes的简称&#xff0c;它是Google的开源容器集群管理系统&#xff0c;其提供应用部署、维护、扩展机制等功能&#xff0c;利用k8s能很方便地管理跨机器运行容器化的应用。 K8s总揽 K8s主…