修改

  • 2020.5.11 修改了反转颜色代码,现在是彩色反转而不是黑白反转。
  • 2020.5.13 解决了一些bug,反转颜色程序更鲁棒了,彩色和黑白都可选了。

前言

最近很忙,作息不规律的生活让我有点起痘甚至觉得自己头发掉的也很快。作为一个计算机专业的求学者,代码和论文是我工作中的两个很重要的组成部分,我经常会厌倦重复无聊的工作,尝试写一些脚本或者设计一些优化的工作流来帮助自己更好的完成工作。当然如果只是简单的完成工作,时间可能会很少,如果要因为某一项工作写一个没有bug的脚本,或者设计一套高效运转的流程会花费大量的时间。

我经常需要阅读论文然后在小组内分享,我认为比读懂论文更重要的是如何将知识传播给别人。自从更换Mac以后,各种展示都在使Keynote来做,极少数的演示文稿使用PowerPoint来做。喜欢Keynote有很多原因,Keynote延续了Apple产品的风格,是一款非常省心的软件,学习成本相比PowerPoint也要更低。同时Keynote的作品下限也很高,你不需要像PowerPoint一样费心苦想如何去实现更优的动画效果,Keynote自带的效果足够应付大多数的演示场景,Keynote自带的模版或者简单的设计就能带来不错的效果。

在论文分享的任务中,论文阅读往往占据了大量的时间预算,如果想要做出传达明确的演示,往往也需要花费很多时间。其实这是一种权衡,如果你使用大量的时间去阅读论文而草草的做了几页ppt,最后的演示效果不会很好,费心阅读的论文精髓并不能得到传达,这很关键。一个好的论文分享Keynote工作流让我从演示文稿的繁重制作中脱身,既能给予更多时间预算来阅读论文又能短时间制作出效果不错表意清楚的Keynote。

工具

我主要使用的就是几个比较简单的工具。

  1. MarginNote 3 for macOS
  2. 截图工具(macOS自带)
  3. Grapher(macOS自带)
  4. Keynote
  5. Paste
  6. Yoink
  7. 负责反转颜色的脚本

我觉得参与工具越少,工具越简单,流程越少的工作流才是健康的可以持续运作的,这里列到的工具只有七个,看似很多,但并非所有都是必须的。其中MarginNote 3 我用来看论文,功能很齐全,这里之所以列到了MarginNote 3 for macOS 是因为论文中会有很多公式或者图标,截图会比较方便,其实只是一个阅读器,你可以使用别的阅读器进行替换。

关于第二个,相信使用过macOS 10.13及以上都用过系统自带的截图工具,非常好用,这里就不多介绍了。

至于Grapher就比较厉害了,它是一款macOS自带的软件,但是我估计没有几个人会知道。Grapher可以输入各种各样的公式,然后输出2d或者3d图形,输入的公式也可以到处到图片或者LaTeX等格式,你可以自定义公式和图形的输出样式可以满足我各种各样的绘图和公式输出。

Keynote就是我们的主角,我最喜爱的是纯黑的模版,也就是库克经常用的那个,非常简约,黑色的背景也让演示内容得到突出。顺便我们的会议室是个小屋子没有窗户,每次演示别人的白色背景非常伤眼睛,相比之下黑色的背景让大家能够让演示更加的护眼,更加的使人专注。

Paste是macOS上一款可以记录你的复制内容的工具,使用这个工具是因为我发现Grapher公式导出的公式图片,没有办法直接导出到Finder(访达)中,但是会被Paste记录可以配合下面的软件使用。

Yoink是一款帮助你复制的软件,如果你有过在macOS的非全屏应用和全屏应用复制过文件的经历,你就会知道在macOS上那种操作非常窒息。Yoink提供了一个靠边缘的小容器,可以接受各种来源的文件,包括Paste中的复制的图片。你可以先将非全屏应用的文件装进小容器中,然后切换屏幕,将其中的文件拖出来,容器会自动清空。我会使用这种办法来将Grapher的导出图片保存下来,方便后面转颜色的脚本处理。尽管这种方法增加了两种工具,不过这两种工具在其他的工作流中也被我大量使用,所以没有增加额外的开销。你也可以使用别的办法将导出的项目保存为文件,这不是必须的。同时Paste和Yoink是Setapp套件中的软件,我有上朋友的车订阅,非常不错。

最后是我自己写的反转颜色的脚本,主要就是用到了Python OpenCV来转灰度图,然后反转颜色的功能。写这个脚本的初衷就是为了更好的让其他工具服务于Keynote,因为大多数的pdf文档是都是在白色背景显示的,白底黑字,如果你想在一个黑的背景下使用这种截图的话,你想像一下在一个黑屋子里面,有的整页只有小部分的白色,突然实验结果页,一屏幕的白色,相信开会的90%眼都会瞎掉。另外Grapher输出的公式和图也都是白底黑字,这样一来翻转颜色的脚本会让工作流顺利的运转,脚本代码我会在文章最后附上,还有些问题,会继续在这个blog更新,因为功能不多就不开源到GitHub上讨骂了。

详细流程

介绍了所有工具之后,我来串一遍完整的流程。

首先你应该先阅读论文,你可以参照这篇文章找找阅读论文的方法。在论文阅读中经常会遇到很多的数学推导(我假设你跟我一样),这时候如果你畏惧了,那么整篇文章读完其实你也迷迷糊糊,因为你根本不知道为什么会这样。(嗯,他的实验结果不错吆,为什么呢???)所以我建议你去勇敢面对数学公式,没有那么难,只要你把英文的数学概念都解决了,然后去查清或者问清楚这代表什么就可以,因为通常论文都会有详细的推导,只要你有高等数学的知识就能够理解。最大的难关可能是语言关,以前阅读的一篇文章中,因为我对unit step function(单位阶跃方程)一直靠意会而没有去查清具体的意思,查了之后发现之前的很多理解都有误。

在你决定直面数学魔鬼之后,你发现只有明白了数学原理之后才能将论文之精髓理解清楚,进而将更详尽的推导或思路演绎演示给他人才能帮助他人也理解论文之精髓。你找到了草稿纸,一本正经的推导,推导成功之后你宛如一个减肥成功的胖子一般开心,但是这个时候你发现一切的努力都在草稿纸上,不能分享的时候把草稿纸的照片贴到ppt上或者直播手推吧,这太二了。

Grapher这个时候就像一个天使出现在你的面前,你可以简单的通过快捷键打出复杂的符号,很快草稿纸上的公式就被录入到了电脑中,相信我,真的很快。(我用过一些沙雕的自称可以识别手写体公式的软件,积分号你都不认识,我们没有办法成为朋友。)同时Grapher也方便的给你提供了图的输出和公式的输出,这样一来你录入的公式或者生成的图就可以导出了,我使用Grapher 配合 Paste 联动 Yoink,只是顺手,并非必须操作,你可以根据自己的情况设计。

完成了数学公式的推导和数学公式的收集之后,你便寻找论文中重要的图表或者重点字段。对于图表重新绘制非常麻烦,截图工具进行截图保存非常方便,对于重点段落或者思想,如果是排版复杂不可拆分的部分,可以截图保存,对于易于总结和分条的应该进行分条总结处理。

完成上述的内容之后,你对论文至少有了大概的了解,核心的算法也已经参透。你现在的任务就是设计keynote将论文精髓演绎出来,一篇精妙绝伦的论文很多时候都不是正叙。你该顺着作者的思路,作者是如何想出这个点子的,作者做了哪些相关的努力才找到正确答案的。这些都写在核心算法和中重点段落中,相信你经过一段时间的重新组织可以找到答案。

接下来将会水到渠成,顺利无比。你将整理到的图片放到一起,使用反转颜色脚本进行处理。你打开Keynote,选择黑色模版,先按照逻辑顺序将标题理顺,然后将转换好的图片置于其中。初版已然完成,接下来的工作就是调整幻灯片的顺序,找出冗余的slide,或者补齐未收集的图表。简单的迭代过后,相信你已经同时完成了备稿和演示文稿的制作。

对Keynote有了充分的熟悉,你就可以准备开始分享了!下面讲一下细节的trick,让它更好的工作。

Tricks

  1. Grapher的公式可以设置字体,尽量调整字号到最大,因为这样输出的公式图片在缩放之后仍然能够保证清楚。如果你同时使用论文中的公式和Grapher输出的公式,这很重要。
  2. Grapher可以设置图中线的宽度,是否有坐标轴以及格点,这些细节设置可以让你的图表更美观。同时输出的图质量以及ppi也可以设置,与上面同理,尽量设高尽管会增加图片的存储大小,但是对缩放会更加鲁棒。
  3. Keynote的MagicMove特效是个好东西,善用。
  4. Keynote可以使用缩进来组织幻灯片,非常适合分部展开的演示。

代码

脚本是可用的但是还有一些功能没有实现,我会在这个地方及时更新脚本,如果有不合理或者写的不好的地方,望多多指正,使用之前请阅读注释确保正确使用。

# This script use opencv to reverse image color.
# Why:
#     When I use keynote to prepare meeting presentation,
#     I want make all keynote in dark, but paper was always a pdf,
#     which is white, so i write this script to batch reverse images color.
# Author:
#     Albert Dongz
# History:
#     2019.11.09 First Release
#     2020.2.29 Integrate in Hades & and comments.
#     2020.5.8 Convert to reverse RGB
#     2020.5.13 More robust
# Dependencies:
#     opencv
# Variable:
#     None
# Attention:
#     1. You should provide the path of images.
#     2. You can re-exec this script when you add new image in your image dir.
#     3. Best wishes to you!
# License:
#     BSD

import cv2
import argparse
import os


def parse_args():
    parser = argparse.ArgumentParser(description='Color Reverse Tool')
    parser.add_argument(
        '-p', '--path', help='dir of images need to reverse.', default="/Users/dongz/Desktop/Dongz/M-Meeting/")
    parser.add_argument(
        '-d', '--dest', help='dir of reversed image to store.', default="./reversed/")
    parser.add_argument(
        '--prefix', help='prefix name of reversed image.', default="reversed_")
    parser.add_argument(
        '-m', '--mode', help='reverse mode', type=str, default="RGB")
    arg = parser.parse_args()
    return arg


def reverse_walk(img_dir, dest_dir, prefix, mode="RGB"):
    for root, dirs, files in os.walk(img_dir):
        root_abs = os.path.abspath(root)
        dest_abs = os.path.abspath(dest_dir)
        img_abs = os.path.abspath(img_dir)
        # ignore dest path
        if root_abs != dest_abs:
            print("Processing " + root_abs)
            # get subdir name
            subdir = root_abs[len(os.path.commonpath([root_abs, img_abs])):].replace(os.sep,'',1)
            for file in files:
                img_path = os.path.join(root_abs, file)
                dst_img = reverse_color(img_path)
                if dst_img is False:
                    # if file is not image file, skip.
                    continue
                else:
                    store_path = os.path.join(dest_dir, subdir, prefix + file)
                    store_dir = os.path.dirname(store_path)
                    if not os.path.isdir(store_dir):
                        os.makedirs(store_dir)
                    try:
                        # If write success, this call will return True.
                        cv2.imwrite(store_path, dst_img)
                    except:
                        print("Sorry, this file failed. " + img_path)


def reverse_color(img_path, mode="RGB"):
    assert mode in [
        "RGB", "GRAY"], "Mode must be RGB or GRAY, check your function call."
    try:
        img = cv2.imread(img_path, 1)
        h, w, _ = img.shape
        if mode == "RGB":
            dst = 255 - img
        elif mode == "GRAY":
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            dst = 255 - gray
    except:
        # Not a image file, return False
        return False
    return dst


if __name__ == "__main__":
    args = parse_args()
    # if dest dir do not exist, make dest dir
    if not os.path.isdir(args.dest):
        os.makedirs(args.dest)
    reverse_walk(args.path, args.dest, args.prefix, mode=args.mode)
    print("Process complete.")

Life is life