OmniAICreator/Qiita-1.07M huggingface dataset:
.pydファイルは、WindowsではDLLそのもの

**.pydファイルは、WindowsではDLLそのもの**、とのことで、勉強になりました。

WindowsでPybind11を使ってC++で自作した関数をPythonにインポートする際は、C++でDLLを作成するタイプのVisual Studioプロジェクトを作成し、`PYBIND11_PLUGIN(...){ ... }`を含むコードを付加、ビルド、DLLを生成、生成した**DLLの名前を.dll→.pydに変更**して、.pyスクリプトファイルと同じフォルダに.pydを配置、.pyスクリプト内からファイル名でimport可能、という仕組みのよう。

(.pydのdは、DLLのd? かなと思ってしまいますが...それなら.pydllと名付ける方が分かり易いはずですが...、ただ、Windows限定の話になってしまうので、違うのかな、など悶々...)

以下引用:

---
Pybind11を使ってC ++をPythonに拡張する
https://www.366service.com/jp/qa/9c02528547c8067355edf43854e3fd4e
>Pythonでは、.pydファイルの名前は、内部のモジュールと同じでなければなりません。ドキュメント( https://docs.python.org/2/faq/windows.html )から:
>
>**foo.pydという名前のDLL**がある場合、関数initfoo()が必要です。次に、Python "import foo"を書くことができ、Pythonはfoo.pyd(foo.py、foo.pycと同様)を検索し、見つかった場合はinitfoo()を呼び出して初期化しようとします。

---
PythonからC++のクラスを使ってみるテスト(pybind11版)
https://qiita.com/tibigame/items/499a81f0e1cb47962b90
>ビルド後に**「ClassTestLib.dll」を「ClassTestLib.pyd」にリネーム**して
>pythonコードがあるディレクトリにコピーする。

---
Python on Windows FAQ
https://docs.python.org/2/faq/windows.html#is-a-pyd-file-the-same-as-a-dll
>Is a *.pyd file the same as a DLL?
>
>Yes, **.pyd files are dll’s**, but there are a few differences. If you have a DLL named foo.pyd, then it must have a function initfoo(). You can then write Python “import foo”, and Python will search for foo.pyd (as well as foo.py, foo.pyc) and if it finds it, will attempt to call initfoo() to initialize it. You do not link your .exe with foo.lib, as that would cause Windows to require the DLL to be present.

[DeepLで翻訳↓](https://www.deepl.com/ja/translator#en/ja/Is%20a%20*.pyd%20file%20the%20same%20as%20a%20DLL%3F%0A%0AYes%2C%20.pyd%20files%20are%20dll%E2%80%99s%2C%20but%20there%20are%20a%20few%20differences.%20If%20you%20have%20a%20DLL%20named%20foo.pyd%2C%20then%20it%20must%20have%20a%20function%20initfoo().%20You%20can%20then%20write%20Python%20%E2%80%9Cimport%20foo%E2%80%9D%2C%20and%20Python%20will%20search%20for%20foo.pyd%20(as%20well%20as%20foo.py%2C%20foo.pyc)%20and%20if%20it%20finds%20it%2C%20will%20attempt%20to%20call%20initfoo()%20to%20initialize%20it.%20You%20do%20not%20link%20your%20.exe%20with%20foo.lib%2C%20as%20that%20would%20cause%20Windows%20to%20require%20the%20DLL%20to%20be%20present.%0A)
>.pydファイルはDLLと同じですか？
>
>はい、.pyd ファイルは DLL ですが、いくつかの違いがあります。もし foo.pyd という名前の DLL があれば、それは initfoo() 関数を持っていなければなりません。Python に "import foo" と書けば、Python は foo.pyd (および foo.py, foo.pyc) を検索し、それを見つけたら initfoo() を呼び出して初期化を試みます。.exe と foo.lib をリンクしてはいけません。
tex algorithm 記述方法

## 概要
texによくあるalgorithmの使い方まとめ

下記のように、`\begin{algorithm}`の中に`\begin{algorithmic}`をいれて、その中に任意のアルゴリズムを記述します。

```tex
\begin{algorithm}
  \caption{Title}
\begin{algorithmic}

ここにアルゴリズムを書く

\end{algorithmic}
\end{algorithm}
```


## 基本操作
### 初期化（Initialize:を定義する）

`\begin{document}`の前に定義しておく。

```tex
\algnewcommand{\Initialization}[1]{%
  \State \textbf{Initialization:}
  \State \hspace*{\algorithmicindent}\parbox[t]{0.8\linewidth}{\raggedright #1}
}
```


```tex
  \Initialization{
  $x$ := $0$
  $y$ := \Call{GetValue}{$x$}
  }
```

![スクリーンショット 2020-12-30 123650.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264708/51d759bf-fc0b-7077-7fdc-999714c822bc.png)

### for all 

```tex
\ForAll{$value\in values$} 
  \State \Call {PRINT}{$value$}
\EndFor
```

![スクリーンショット 2020-12-30 123708.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264708/de6557a3-9e60-7954-990e-86ba09fc52fa.png)

### if/else if/else
```tex
\Function{CheckNumber}{$x$}
    \If{$x > 10$}
      \State \Call {PRINT}{$``xは10より大きい"$}
    \ElsIf{$x >= 5$}
    　\State \Call {PRINT}{$``xは5以上10以下"$}
    \Else
      \State \Call {PRINT}{$``xは5未満"$}
```

![スクリーンショット 2020-12-30 124340.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264708/cb421187-efef-c675-df92-42f6f9201809.png)




## おまけ
文字列等でダブルクォーテーションを使いたい時

```tex

``文字列"
```

## 参考
http://li-feel.hatenablog.com/entry/2017/12/19/160618
https://github.com/PMOB/study-tex/wiki/reference-algorithm
PowershellでLotusScriptを実行する

[PowershellでLotusScriptを利用する](https://qiita.com/fumimix/items/13a74e534bc5f2b6aaae)の記事を読んで、PowerShellでLotusScriptのクラスがどこまで使えるのかを検証してみました。
※ New-ObjectによるCOMオブジェクトの作成は、[[マイクロソフトのドキュメント]](https://docs.microsoft.com/ja-jp/powershell/scripting/samples/creating-.net-and-com-objects--new-object-?view=powershell-7.1)を参照しました。
※ リンク先の記事にあるように、32Bit版PowerShellでなければLotusScriptは実行できません。

#検証したこと
個人アドレス帳の全文書からUNIDと設計要素（フォーム）を取得するコードを記述して、以下の動作を確認しました。
* Notesデータベースの情報取得
* Notes文書の情報取得
* 繰り返し処理の実行

```PowerShell:個人アドレス帳（ローカル）の情報を取得する
$ss = new-object -comobject Lotus.NotesSession
$ss.Initialize("")
# 個人アドレス帳(ローカル)を呼び出す
$db = $ss.GetDatabase("","names.nsf")
#Notesデータベースの情報取得 
#個人アドレス帳(ローカル)の全文書を取得する
$col = $db.AllDocuments
Write-Host "DB内の文書数は : " $col.count
$doc = $col.getFirstDocument()
#繰り返し処理の実行
while ($doc -ne $null)
{$item = $doc.UniversalId
$form = $doc.GetItemValue("Form")
$i++
#Notes文書の情報取得
Write-Host $i "番目の文書IDは : " $item
Write-Host $i "番目の文書フォームは : " $form
$doc = $col.getNextDocument($doc)
}

pause
```

実行結果
![実行結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/983034/028b025d-89d9-2f2c-5ed5-eeb0eb7047cf.png)
#感想
単純な処理（特にデータを抽出するだけの処理）であれば、デグレーションを引き起こす可能性があるNotesデータベースの改修ではなく、PowerShellを利用したほうが良いのではと感じました。
windowsで最初にやること - 2021年版

[前回の記事](https://qiita.com/honeytrap15/items/e6e7f65bb436a4b5d813) をいまだに見返してセットアップしたりするけど、色々覚えたり、ツールの普及によっていろいろ状況が変わったので最新版をメモ

※ツールのセットアップや実行環境の使いやすさは人によって違うのであくまで個人的な設定のメモです。

# ユーザ登録

Microsoftアカウントで初回セットアップすると、ユーザ名がおかしくなるので新規ユーザを任意のユーザ名で作成して、作成後にMicrosoftアカウントを紐づける。

# ターミナルのインストール

Windows TerminalをMicrosoftストアからインストール

# WSL2

[公式の手順](https://docs.microsoft.com/ja-jp/windows/wsl/install-win10) でWSL2をインストールする

インストール後に win + r で `optionalfeatures` を起動して、`Windows プログラムハイパーバイザー プラットフォーム`を有効にする。

これを有効にしておくことでWSL2とvirtualboxを併用できるようになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70029/ee6df71f-255e-5f91-6404-5d3c7f0c32e5.png)

# VirtualBoxをインストール

[公式ページ](https://www.virtualbox.org/)からインストーラーをダウンロードしてインストールする。

# scoop

[公式の手順](https://scoop.sh/)に従ってscoopをインストールする。

scoop インストール後はscoopで任意のソフトウェアをインストールできる。

# Linux環境

WSLのディストリをMicrosoftストアからインストールする。
お問い合わせにletter_opener_webを使ってみる


今作っているサービスに、お問い合わせ機能を作ることにしました。
letter opnerというgemがあることを知り、なんだか極めて楽そうだと思ったので（途中エラーが出ましたが解決）使ってみることにしました。

色々な記事をみていると、意外と「ここは当たり前にできてますよね」前提で省かれているところが有って（私が気がつかなかっただけ）結構困ったので、自分の備忘録もかねて書いておきます。

## 環境
ruby 2.6.6
Rails 6.0.3.3
## 実装

```ruby:Gemfile
group :development do
   gem 'letter_opener_web'
end

```
bundle installします。

scaffoldを使って一気に作ります。

```
$ rails g scaffold Contact name:string email:string content:text
```
rails db:migrate
します。
ローカル環境で　/contacts/newにアクセスすると
![スクリーンショット 2020-10-31 12.41.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139887/a51a895f-1760-637a-7212-44446168f522.png)
こんな簡易の入力画面ができていたらまずはOKです。

Action Mailerを作ります。

```
$ bin/rails g mailer ContactMailer
```
送信機能の実装をします。

```ruby:app/mailers/contact_mailer.rb
class ContactMailer < ApplicationMailer
    def contact_mail(contact)
        @contact = contact
        mail to: "自分のメールアドレス",subject: "confirmation"
    end
end
```
この記事を下書き状態にしてて忘れていました。
もしかしたら何か抜けてるかもしれない。。

いざ、コンタクトフォームから送信すると、
![スクリーンショット 2020-12-30 12.25.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139887/d8e939bf-25e4-2bcb-8c76-ee90b88a84c8.png)
このようなメールがちゃんと作れましたよ、という画面に切り替わります。

letter_opnerの送信確認
http://localhost:3000/letter_opener
でアクセスできます。こんな画面が表示されて、送信されたメール内容の通知があればメーラーがちゃんと動いてますよ、ということ。
![スクリーンショット 2020-12-30 12.28.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139887/bf918a76-1efd-5e22-f2ab-1967b2ccbb9e.png)



この後本番環境で実装してなかったな！と思い、sendgrid使おうとしたら、sendgridのアドオンを追加しても、ページにアクセスできない問題が。
どうやらsendgridの新規登録をして、ログイン情報を得てからでないと設定ができない模様。（年明け持ち越し確定）

## 参考にさせていただいた記事
https://papa-programing.jp/rails-contacts-function/
むしろこちらの記事のが親切です、、色々自分が見落としていた部分も！
【3D OD LiDAR編】3DSSD : point sampling&gropingで物体の候補を提案

<h1>3DSSD: Point-based 3D Single Stage Object Detector</h1>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482094/3310868c-69a5-afcb-94e4-1f8a07e91ac5.png)

従来の研究ではFeature Propagation(点群から特徴量を取り出すネットワーク pointnet++など)、と2stageのrefinementが全体の半分以上の計算時間を使っているとした。だから2つのModuleを取り除き1st stageの高速のネットワークを提案するのがこの論文の趣旨。

<h1>アルゴリズム</h1>

<h2>BackBone</h2>
1. Pointをsamplingする
2. 選ばれなかったpointは選ばれたpointの中から一番近い物にGroupingする
3. MLPでGroupingされたPointの特徴を学習する
4. Max PoolでGroup全体のpointの特徴を抽出
5. 何回か1~4をくり返す


<h3>F-FPS(Feature-Farthest Point Samping)とD-FPS(Distance-Farthest Point Samping)の組み合わせPoint選択</h3>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482094/488ae53f-7e5f-adc7-d1ab-a8bb509baa9b.png)
F-FPS)はSemantic infomationの距離でpointをサンプリングするので、車であれば窓やタイヤフレームなどは3次元空間的には近くにあるが、Semantic infomation的には遠いので多様なpointがsamplingできる。しかし、同じ種類の違う物体(ex 2人の人)のFeatureが近くなってしまい、F-FPSだけだと片方のpointしかsamplingされない可能性がある。
そこでD-FPSと組み合わせることで、Euclidian Disatanceで別の物と認識する事が出来る。





<h2>Candidate Generation Layer</h2>

1. F-FPSでsamplingしたpointの特徴量から物体の中心のshift(x,y,z)を推定する。
2. F-FPSでsamplingしたpointのXYZをshiftさせる
3. D-FPSでsamplingしたpointとF-FPSでsamplingしたpointを、2でshiftさせたpointから距離の近い点にGroupingする。(ここでGroupがNm/2個出来る)
4. MLPでGroupの特徴を学習する
5. Max PoolでGroup全体のpointの特徴を抽出(Nm/2個のGroupがそれぞれCm個の特徴量を持つ)

<h3>Anchor-free Regression Head</h3>
Anchor-baseの手法はいくつもAnchorを定義しないといけなく、計算量が多くなるからAnchor Freeにしたらしい。

<h2>Prediction Head</h2>
1. それぞれのGroupからBounding BoxとClassを直接推定する(Nm/2個の物体が出力)

<h1>結果</h1>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482094/6f8f583c-8e48-14a9-23fc-e380dd941558.png)
1stage detectorの中では一番精度が高い！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482094/9658b850-62f6-4cbc-b2b4-75d6797652b8.png)
Point-baseの方法の中では速い。


<h1>結論</h1>
・Pointをsampling&groupingしてBounding Boxの候補を提案するというアイディア方法って面白い

<h1>参考文献</h1>
3DSSD: Point-based 3D Single Stage Object Detector
https://arxiv.org/pdf/2002.10187.pdf
VeeValidate3で複数のフィールドをバリデーションする

# 環境
* Vue 2.6.11
* VeeValidate 3.2.5

# はじめに
VeeValidate3系ではValidationObserverとValidationProviderを使ってバリデーションを行いますが、ValidationProviderは単一のフィールドが対象になります。開始終了時間のように複数のフィールドをセットでバリデーションしたい場合のやり方が分からなかったので調べてみました。

# コンポーネント化
VueではUIコンポーネントを自作できるため、複数のフィールドを持つコンポーネントを作成して、コンポーネントのv-modelに対してバリデーションをするようにします。

```vue:Form.vue
<ValidationProvider ref="provider" v-slot="{ errors }" rules="fromToRequired">
  <FromToText v-model="fromToTime" />
  <span>{{ errors[0] }}</span>
</ValidationProvider>
```
```vue:FromToText.vue
<template>
  <div>
    <input type="text" v-model="text.from" />
    <span>～</span>
    <input type="text" v-model="text.to" />
  </div>
</template>

<script>
  export default {
    props: {
      value: Object,
    },
    data () {
      return {
        text: {}
      }
    },
    mounted () {
      this.text = this.value
    }
  }
</script>
```
v-modelに設定しているfromToTimeがObject型になっているところがポイントです。こうすることで一つのコンポーネントで複数のフィールドを扱えるようになります。後はこのObject型の変数のバリデーションを作成すれば完成です。

```vue:Form.vue
<script>
  import { ValidationObserver, ValidationProvider, extend } from 'vee-validate'
  import FromToText from '@/components/UIComponents/Inputs/FromToText'

  export default{
    components: {
      ValidationProvider,
      ValidationObserver,
      FromToText
    },
    data () {
      return {
        fromToTime: {}
      }
    },
    beforeCreate () {
      extend('fromToRequired', {
        validate: (value) => {
          if (value === null || value === undefined) {
            return {
              valid: false
            }
          }
          return {
            valid: value.from && value.to
          }
        },
        message: '開始と終了を両方入力してください。'
      })
    }
  }
</script>
```
composerでインストールしたlaravelをアンインストールする

# 目的

- Macにcomposerを用いてインストールしたlaravelをアンインストールする方法をまとめる

# 情報

- 下記コマンドを実行してlaravelをインストールした直後にlaravelをアンインストールする方法をまとめる。

    ```terminal
    $ composer global require laravel/installer
    ```

- Macに直接インストールしたlaravelをアンインストールする方法をまとめる。

# 方法

1. 下記コマンドを実行してcomposer.jsonファイルを開く。

    ```terminal
    $ vi ~/.composer/composer.json
    ```

1. 記載されている内容を削除する。`{}`は残す。
    - 修正前

        ```~/.composer/composer.json
        {
            "require": {
                "laravel/installer": "^4.1"
            }
        }
        ```

    - 修正後

        ```~/.composer/composer.json
        {

        }
        ```
1. 下記コマンドを実行してcomposerのディレクトリまで移動する。

    ```terminal
    $ cd ~/.composer
    ```

1. 下記コマンドを実行してcomposerのパッケージ情報を更新する。

    ```terminal
    $ composer update
    ```
Visual Studio Code の Remote SSH 経由でファイルを開いているときに Visual Studio Code の色を変える


# Visual Studio Code の Remote SSH 経由でファイルを開いているときに Visual Studio Code の色を変える

[Visual Studio Code の Remote SSH で Linux 上のファイルを直接編集する](https://qiita.com/m-tmatma/items/f3cc666c2e667adab784) の記事で書いたように、SSH 経由でリモートサーバーにあるファイルを直接編集できます。この際、どの リモートサーバーか色で判別できると非常に便利です。[Visual Studio Codeエディタの色をプロジェクトごとに違うものにする](https://qiita.com/kabosu3d/items/3afad300ef1ea9ddd50b) という記事を参考にして、リモートサーバー側の色を変えます。

## 事前準備

[Visual Studio Code の Remote SSH で Linux 上のファイルを直接編集する](https://qiita.com/m-tmatma/items/f3cc666c2e667adab784) の記事の通りにリモートサーバーにアクセスできるようにしておく。

## 設定

### リモートサーバーに接続

左端の pane の青丸を押した後、赤丸を押してリモートサーバーにアクセスする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/500246/34fa43a7-df88-f19e-f3d8-aff850bc3b62.png)

### 設定メニューを表示

新しく開いたウィンドウで　`ファイル`メニューから`ユーザー設定`、`設定`を選ぶ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/500246/b8a0ae81-42d1-1e16-378e-d11b09ecdb92.png)

### 設定対象のリモートサーバーを選ぶ

`リモート [SSH: 192.168.11.50]` のシートを選ぶ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/500246/92eb46cd-ff54-91a7-188d-23dca9e96483.png)

### 色を設定するために `settings.json` を開く。

`ワークベンチ` の `外観` を選び、`settings.json` で編集を選ぶ。

### `settings.json` の設定を反映

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/500246/631c6a81-118f-6e47-40e9-5cb5f1d5eb14.png)

[Visual Studio Codeエディタの色をプロジェクトごとに違うものにする](https://qiita.com/kabosu3d/items/3afad300ef1ea9ddd50b#%E6%89%8B%E9%A0%86) という記事の、`手順` に記載の設定をエディタ上に張り付ける。

設定ファイルを保存した瞬間に visual studio code の色が変わります。

### `settings.json` の保存先

Visual studio code で開くファイル名は以下の形式になっている。
`ユーザー名` の部分は SSH のログイン先のユーザー名です。

```
/home/ユーザー名/.vscode-server/data/Machine/settings.json
```

### 設定後

設定後は以下のような感じになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/500246/a6a131ec-f11c-1a11-ca1b-00a378914bd6.png)
Githubのオーガニゼーション新設とリポジトリ移行時の手順・注意点

## 概要
利用中のGithubオーガニゼーションから別のオーガニゼーションを作成し、リポジトリを始めとして各種設定を移行しました。
その際の手順や注意点をまとめたものです。

## 環境
機能比較をした上で開発に支障はないと判断し、GitHub Platinumプランからfreeプランに移行しています。
Platinumは料金プラン改訂前の組織向けプランとなります。

現在のプランごとの機能比較はこちらで出来ますので、事前に機能と料金を確認してから移行が必要となります。
https://github.co.jp/pricing.html
特に、**旧Platinumプランではリポジトリ数課金のためユーザ数の制限がなかったのに対して、新プランではユーザ数課金となっているので移行するユーザの必要性について精査が必要です。**

## Github側作業

### 新オーガニゼーション開設
オーガニゼーションのownerとなるGithubアカウントで右上の「New organization」を選択し、プランやオーガニゼーション名を登録します。この時に空のteamを作成します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/e35f7815-eb50-5337-5b9a-2623d0478d8f.png" width=30%>

連絡用メールアドレスも登録しますが、オーガニゼーション作成ユーザ自身のアドレスでなくても問題ありません。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/e5b21d17-539c-7872-e1f7-42d0cc7b7d2c.png" width=60%>

### リポジトリの移行
既存リポジトリのbranch、issues、Pull Requestの内容などほとんどそのまま移行が出来る[Transfer ownership機能](https://docs.github.com/ja/free-pro-team@latest/github/administering-a-repository/transferring-a-repository)を使って移行します。リポジトリサイズにもよると思いますが数十秒で移行可能です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/1813c666-de01-98f6-eeb0-d721f6182628.png" width=60%>
    
### リポジトリの権限追加
デフォルトでは、招待したユーザはリポジトリに対してread権限となっているので、必要なユーザやteamに対してwriteに切り替えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/462812e3-9da9-f08d-0bda-d9c3805a3804.png)

### 連携アプリの追加
#### slack
Githubのイベントをslackで通知している場合は、Githubのmarketplaceからslackを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/95969d78-f712-da37-e764-37def15db621.png)

追加後、slackの通知先のチャネルにて購読するイベントを設定設定します。

```
/github subscribe new_organization/{repository} reviews comments commits:all
```

#### CircleCI
リポジトリをCircleCIで連携している場合は、Githubのリポジトリを移行すると自動的にCircleCIでもオーガニゼーションが移行されるので、プロジェクト、過去のCI実行履歴が移行されているか確認します。

#### 無料プランへの変更による影響
CircleCIで有料でしか利用できない機能を有効にしていると、テストが失敗してしまいます。
今回はテストのパフォーマンス向上のためにDockerビルドイメージの`Docker Layer Caching`が有効になっていたため、一旦無効にしました。こちらは従量課金制プランで利用可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/5bc8c16c-fdd7-ba65-0bf9-38f720386288.png)

## サーバ側作業

### URL変更対応

* 移行前のオーガニゼーションのGithubのURLを叩いても新URLにリダイレクトされる仕様となっておりますが、リポジトリのデプロイ先サーバでの.git/configファイルの`remote  origin`の値を修正しておきます。
* サーバに対して自動で`git pull`が走ってるリポジトリについてはタイミングに注意します。

```
# 修正前
[remote "origin"]
        url = https://github.com/current_organization/repository_name.git

# 修正後
[remote "origin"]
        url = https://github.com/new_organization/repository_name.git
```

ファイル更新後、サーバ上で`git remote -v`してリモートのURLが変更されていることを確認します。

## ローカル側作業
サーバ側作業と同様、.git/configファイルを更新して`git fetch`や`git clone`が出来ることを確認します。

## ユーザの招待
設定完了したら既存のオーガニゼーションからユーザを招待し、作成したteamに割り当てて作業完了です。

## 課題
freeプランに変更したことによって以下のような機能制限がありますが、今のところ運用でまかなえているので経過注視としています。

* リポジトリを保護するために設定する`git push --force`の禁止を設定できない。
* PRレビューやIssueを複数人にアサインできない。
nodejsでtail

electron(nodejs)上で特定のファイルをtailする

tailライブラリを使用する。
https://github.com/lucagrulla/node-tail


```
$ yarn add tail
```


```App.tsx
import React, { useEffect, useState } from 'react'
import { render } from 'react-dom'
import { Tail } from 'tail'

const tail = new Tail('file-path')

const App = () => {
  let [texts, setTexts] = useState<string[]>([])

  useEffect(() => {
    tail.on('line', (data : string) => {
      texts = [...texts, data]
      setTexts(texts)
    })

    return () => {
      tail.unwatch()
    }
  }, [])

  return (
    <>
      {
        texts.map((text, index) => {
          return (
            <div key={index}>{text}</div>
          )
        })
      }
    </>
  )
}

```
CSS関数の活用

cssで一つのタグの中に同じ指定をするのって面倒じゃないですか？
統一感を出せば見やすくなりますし、デザイン性もあります。
今までのようにいちいち指定して、もしやっぱり変更したいってなったとき一個一個訂正しなければなりません。
でも、関数を宣言していれば、その箇所だけを訂正するだけで済みます。
このようなメリットがあるのでぜひ使用することをオススメします。

では、具体的な使用方法を見ていきましょう。
例えばh1タグの中に文字、下線を指定し同じ色にする場合を見てみましょう。
記述法ですが、
h1 {
--my-color:red
color:var(--my-color);
}
このように文字の前にハイフンを２回つけるのがポイントです。
「--my-color」を宣言し、「var」を付けて入力するだけです。

h1 {
--my-color:red
color:var(--my-color);
border-left:5px solid var(--my-color);
}

こんな感じですね。
ただ、これはh1タグの中でしか効果がなく、これを他のタグでも使いたい時もありますよね。
そんな時には継承して使うことができます。
やり方は簡単です。
h1と他のタグの親に宣言すればいいのです。
仮にbodyが親であれば、

body {
--my-color:red;
}
h1 {
color:var(--my-color);
border-left:5px solid var(--my-color);
}
p{
color:var(--my-color);
}

これでOKです。もしhtml全体に適用したい場合であれば、
上記のbodyの箇所を:rootにすれば良しです。

ここで注意点ですがpxなどの単位をつける場合であれば、必ず「宣言時に付けましょう。」
paddingを付けたい場合
--my-margin:50px;としてあげましょう。
h1 {
--my-margin:50px;
margin-right:var(--my-margin);
とします。

どうしても単位を付けたい場合であれば少し応用ですが、calc()を使いましょう。
どのようにするかというと

body {
--my-margin:50;
}
h1 {
margin:var(calc(--my-margin)*1px);
}
１pxをかけることでmarginが追加されます。

まとめると、CSS関数は
「メリット」
・まとめて同じ指定ができる。
・宣言箇所を訂正するだけで全て変更可能。

「注意点」
・単位の扱い（どうしても付け加えたい場合はcalc()を使いその後に使いたい単位で掛けてあげる）

とても便利な指定方法なので是非使ってみてください。
以上CSS関数でした。
jsをgz配信でめちゃスコア上がった話し

### 目的：
担当サイトのパフォーマンス改善のために、jsを gz配信したい

### 施策：
上記を実現するためにapache側の設定(.htaccess)が必要と言うフロントの依頼

### 結果：
JSが4分の1程のサイズになり、めちゃスコアが上がった

## .htaccess ファイル設定
.htaccess ファイルは元々あったので既存ファイルに下記の内容を追加
![スクリーンショット 2020-12-30 11.15.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274686/74f38a18-9a9c-8000-a5f0-a4f36e637041.png)

上記の設定で、
・gzipを受け入れてくれるブラウザには圧縮した xxx.js.gz を返し
・そうでないブラウザに対しては元の xxx.js を返すはず

## .css設定
.js だけでなく .css も gzip圧縮して設置したいのならば、(js) を (js|css) に設定変更）

## 文字化け問題
上記の設定だけではgzipファイルがエンコードされる際に文字化けしてしまう
![スクリーンショット 2020-12-30 11.15.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274686/bb78efd3-bcdb-4cbc-4d8b-f4ffa4825ff1.png)


### ForceType application/x-javascript :
gzip圧縮したファイルをHTTPレスポンスとして送信すると、Content-Typeがapplication/gzipになってしまい、ブラウザがContent-Typeを正しく認識できなくなる

上記の状況を回避するためにForceTypeを使って、JavaScriptの場合にはapplication/javascriptとなるようにする

### AddEncoding x-gzip .gz :
HTTPレスポンスのContent-Encodingにgzipを指定し、拡張子.gzを含むファイル名がgzip圧縮されていることを伝える

### Header append Vary Accept-Encoding env=!dont-vary
gzipに対応していないブラウザにgzip圧縮されたファイルが送信されるため、ブラウザは受信したファイルを処理できなくなるらしい

Header append Vary Accept-Encoding env=!dont-vary追加で
プロキシサーバーが同じ値のAccept-Encodingを保持している場合のみにキャッシュを利用し、それ以外の場合はWebサーバーにファイルを取得できる

## httpd.confでの設定方法
少しだけ書方が違う
![スクリーンショット 2020-12-30 11.16.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274686/dfac75e9-ce70-0a0f-ae9c-9a09ab52e12b.png)
Mac入門に関する記事リンク

# 私がまとめた記事まとめ

##１．Mac入門〜画面編（Dock）
https://qiita.com/maria-satb/items/60cce1734e09a094bebe

##２．Mac入門〜画面編（Finder）
https://qiita.com/maria-satb/items/3d9e0a8d03c91049c781

##３．Mac入門〜画面編（ディスプレイ設定）
https://qiita.com/maria-satb/items/0992b00ca700c8edbadd

##４．Mac入門〜WindowsとMacの違い
https://qiita.com/maria-satb/items/ff71491f4679f7dba076

##５．Mac入門〜利用前に行いたい設定
https://qiita.com/maria-satb/items/8a2384625682205d092d
Elasticsearch を始める前に学んだ 5つのこと

### 1. Elasticsearch は全文検索を行うソフトウェアである
検索システムは、検索対象のデータ本体に加えて、それらの「メタデータ」も合わせて格納する。
メタデータは、データ本体を探しやすくするために付加する属性、キーワード、頭文字などの索引データのこと。

全文検索の手法としては、以下のようなものがある。
##### １．逐次検索:
　　検索対象を最初から最後まで順次走査を行い該当する箇所を探し出す。
　　Unix の grep コマンドなどがこの方法で検索を行っている。
##### ２．索引型検索：
　　検索対象に予めインデックス（索引）を作成しておき、それを元に該当する箇所を探し出す。

Elasticsearch（多くの検索ソフトウェア）では、索引型検索により全文検索を行っている。

### ２. Elasticsearch は検索サーバレイアに該当するソフトウェアである
一般的な検索システムは、以下の3つの層（レイヤ）で構成される
##### １．検索ライブラリ レイヤ
インデクサ(indexer)とサーチャー(searcher)の機能を提供する。
インデクサ：ドキュメントからキーワードを抽出し、インデックスのデータベースを作成する役割。
サーチャー：インデックスから検索するための検索クエリの機能を提供する役割。
代表的なソフトウェアとして、"Apache Lucene" が挙げられる。
※"Apache Lucene" は、転置インデックス方式の検索機能を提供するソフトウェア。

##### ２．検索サーバ レイア
検索ライブラリ単体で提供される機能を補完するために、インタフェースや管理機能を提供するレイヤ。
代表的なソフトウェアとして、"Apache Solr" や "Elasticsearch" が挙げられる。
※"Apache Solr" や "Elasticsearch"は、Lucene を補完するように、REST API のインタフェースを提供している。

##### ３．検索システム レイヤ
クローラ(crawler)機能や Web ユーザインタフェースを提供する。
Webページ等から、最新のドキュメントを収集する役割で、通称「エンタープライズサーチ」呼ばれる。
代表的なソフトウェアとして、"namazu" や "Fess" などが挙げられる。

Elasticsearch は検索サーバとしての役割を持ち、内部では検索ライブラリである"Apache Lucene"を利用している。
※ "Apache Solr" も"Apache Lucene"を利用していて、基本的な機能に大差はない

### ３. Elasticsearch は1世代前までのメジャーリリースに対して、インデックスの後方性互換がある
Elasticsearch で作成したインデックスが利用できるのは、1つ先のメジャーリリースまでという制限がある。
例えば、Elasticsearch 6.x で作成したインデックスは、Elasticsearch 7.x にバージョンアップした後でも使えるが、Elasticsearch 8.x では使えない。
最近は Elasticsearch のバージョンアップが盛んであることから留意しておきたい。

### ４. Elasticsearch は複数ノードへのデータ分散配置により高速化と高可用性を実現している
Elasticsearch で作成したインデックスは、シャードと呼ばれる単位に分割されて格納される。
Elasticsearch 7.x のデフォルトでは、１インデックスに対するシャードの数は１だが、データを保持するノード数に応じて、シャードの数を調整する。
複数台のノードに分散して配置することで、1台当たりのリソース負荷を減らす狙いがある。
一般的にこの仕組みをシャーディング（パーティショニング）と呼ぶ。

##### メリット１：検索性能の向上
ノードを増やすことで、検索が分散処理で行われるため、その分検索性能が向上する。
Elasticsearchでは、ノードを追加した際のシャード再構成は自動で行われるため、ノードを追加するだけで性能がスケールされる。

##### メリット２：可用性の向上
各シャードに対して、レプリカを1つ以上持たせることが可能。
そのため、仮に1台のノードがダウンしたとしても、レプリカを元にデータを自動復旧することが可能。
デフォルトでは、各シャードに対して1つのレプリカが生成される。

### ５. Elasticsearch ではすべての操作を REST API 経由で行うことが可能
※厳密にすべてかどうかは分からないですが、定義されている
Elasticsearch にデータを入れる、あるいは入っているデータに対しての操作は、REST API が提供されている。

また、インデックスやクエリの対象となるドキュメントはすべて JSON フォーマットで表しています。
Elasticsearch では、1件のレコードのことを「ドキュメント」と呼びますが、JSON オブジェクトとして表現できさえすれば、そのドキュメントを格納するだけで、各項目にインデックスが作成されて、素早くデータ検索ができるようになっている。
※Elasticsearch が誕生する前、前身である "compass" というソフトウェアがあった。それを Elasticsearch として作り直した背景として、REST API をフルで提供することや、JSON がRFC4627として策定されたことがあったと思われる。
DBのデータを連想配列で取得し、そのデータをoptionタグに使う

ポイントは
・$result->fetch_assoc()により、連想配列としてデータを取得
・optionタグを記述する箇所は、whileの中

```php

// DB接続の記述は省略


 <?php
 $result = mysqli_query($con, 'SELECT * FROM table_name');
 ?>

<select name="name">
 <?php
 // $dataがある限りoptionタグを生成する  
 while ($data = $result->fetch_assoc()) {
 ?>
<option value="<?php print $data['id']; ?>">
 <?php print $data['name'].": ".$data['point']."P"; ?>
</option>
 <?php
 }
 ?>
</select>
```
grepQL - Graphql IDE をリリースしました

Graphqlの開発をより簡単にできるようなサービスを作ってみました！

<script async src="https://grepql.netlify.app/" charset="utf-8">https://grepql.netlify.app/</script>

### grepqlとは

- graphql schemaからのquery/mutation自動生成
- grahpql schemaに変更があればdiffの表示
- githubアカウントを利用したschemaの共有

![schema-diff.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/990680/52a3a0dd-68ff-6620-b7c8-4cf3e3d82f07.png)


よろしければ[producthunt](https://www.producthunt.com/posts/grepql)の`upvote`の方もよろしくお願いします :smiley:
Grove IoT スターターキットで温湿度センサを使う

１．概要
だいぶ間が空いてしまいました。前回、SORACOM Harvestへの接続が出来たので、今回は温湿度センサ(Grove DHT11)をつないで測定してみることとしました。


２．内容
今回もスケッチ例を利用することとします。

・以下の順にたどります。
　スケッチ例　→　WioLTEforArduino　→　soracom　→　soracom-harvest　
![201230-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668128/86641293-fa8c-0ce7-d246-67d886c3604b.png)

・温度センサを有効にする
//#define SENSOR_PIN    (WIOLTE_D38)　
コメントアウトを外して、温湿度センサを有効にします。

・送信インターバルを変更する
//#define INTERVAL        (60000)
初期値が60秒(60000)になっているので、確認しやすいように10秒(10000)に変更しました。

・プログラム書き込み
DFUモードに切り替えてからプログラムを書き込みます。
（裏面のBootボタンを押しながら起動するとDFUモードになります。）

TeraTermに表示されたデータと同じデータがSORACOM Harvest上で確認出来ることが分かります。

![201230-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668128/351389f7-43f1-c5fc-98f8-89753339e780.png)



３．まとめ
今回はセンサを接続してデータ取得出来ることを確認しました。
サンプルコードを利用すれば、ほとんど何もプログラムすることがないので簡単に、SORACOMの機能を確認出来るので非常に便利です。
次はほかのセンサも追加したいと考えています。例えば、GPS辺りで位置情報を取得出来ると面白そうな気がします。そもそもSORACOMのSimそのもので位置情報も取得できるので比較してみたいとも思います。
位置情報が取れるようになったら、移動しながらロギングとかもやってみたいと思います。
Raspbirry Pi OS に NGINX をインストールする

#環境
Raspberry Pi 2 Model B
Raspberry Pi OS Lite

#nginxをインストール
すべて以下のサイト通り、、（忘備録ですみません）
（参考：http://igarashi-systems.com/sample/translation/raspberry-pi/remote/remote-web-server-nginx.html ）


    $ sudo apt install nginx

サーバを起動

    $sudo /etc/init.d/nginx start

Raspberry Pi OS 上の場合、`http://localhost/`を開く。
同じネットワーク上の別のコンピュータからの場合、`RapberryPiOSのIPアドレス（例：http://192.168.1.10）`を開く。

「RapberryPiOSのIPアドレス」を調べる方法は以下の通り、

    $ hostname -I


#WEBページの変更
NGINXのWEBページは、`/etc/nginx/sites-enabled/default`に書かれているところを表示する。

```
   root /var/www/html;
```
上記の場合、`/var/www/html`内に、置かれた物が公開される。
自分の場合、ここには`index.nginx-debian.html`が置かれており、これがデフォルト表示されていた。（これを変更すれば、表示が変わる）
（参考：https://k-koh.hatenablog.com/entry/2019/11/08/100158 ）
Apacheの再起動やロード先ファイルのパスを確認してもphp.iniの変更が反映されない時にすること

###はじめに
つまりにつまったので。
小さなことだったのですが悩んでいる方の参考になれば。

###環境
CentOS 8.2 (conoHa VPS)
PHP 7.4.13

###やったこと
PHP-FPMの再起動。以下のコマンドを実行する

```
sudo service php-fpm restart
```

Apacheの再起動やConfiguration File (php.ini) Pathのルート確認、Loaded Configuration Fileの確認は何度もしていたけれどPHP-FPMの再起動は試していませんでしたというお話でした。
上記の有名な原因は情報が豊富だったのですが、こちらの事象は当たり前すぎて（？）見当たらなかったので備忘録も兼ねて。

<参考>
https://stackoverflow.com/questions/51037748/php-7-2-no-additional-ini-files-parsed
Node.js: bluebird を使って Redis のデータを読む

次のプログラムを bluebird を使って書き換えました。
[Node.js: Async/Await を使って Redis のデータを読む](https://qiita.com/ekzemplaro/items/277fce6168dab745264f)

ライブラリーのインストール

```bash
sudo npm install -g bluebird
```

```js:bluebird_read.js
#! /usr/bin/node
// ---------------------------------------------------------------
//
//	bluebird_read.js
//
//					Dec/30/2020
// ---------------------------------------------------------------
'use strict'

const bluebird = require("bluebird")
const redis = require('redis')
bluebird.promisifyAll(redis.RedisClient.prototype)
bluebird.promisifyAll(redis.Multi.prototype)

// ---------------------------------------------------------------
async function proc01 ()
{
	const redisUrl = 'redis://127.0.0.1:6379'
	const client = redis.createClient(redisUrl)

	const keys = await client.keysAsync('*')

	keys.forEach(async function(key,index)
		{
		const value = await client.getAsync(key)
		console.log(key,value)
		})

	await client.quitAsync()
}

// ---------------------------------------------------------------
console.error ("*** 開始 ***")
proc01()
console.error ("*** 終了 ***")

// ---------------------------------------------------------------
```

実行スクリプト

```bash
export NODE_PATH=/usr/lib/node_modules
./bluebird_read.js
```
【3D OD LiDAR編】Part-A2 : オブジェクト内のパーツ(点)の位置の推定

<h1>From Points to Parts: 3D Object Detection from Point Cloud with Part-aware and Part-aggregation Network</h1>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482094/7c942501-e380-e9f3-612a-415ea358f563.png)

[PointRCNN](https://qiita.com/minh33/items/468037a5d244aeedc513)を4つの点を変更した事で精度をあげた。内容多いから、面倒な人は結論から読んで。
*水色で隠した部分はAnchor-freeのmethodだが、困惑するから今回は無視。(精度も低い)


<h2>Stage-I: Part-aware 3D proposal generation</h2>

<h3>Point-wise feature learning via sparse convolution</h3>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482094/2209dd62-85e6-177a-28d9-e998522e2f71.png)

poinnet++を使うよりVoxelizationして3D Sparse Convolutionを使ったほうが効率的らしい

Voxelのサイズを小さくすればRaw point cloudとほとんど差がなく、結果に大きく影響しないとの主張


<h3>Estimation of foreground points and intra-object part locations</h3>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482094/79d67aa6-6597-4cf7-56ff-3d5aa8327594.png)

従来研究ではPointが前景に属するか、後景に属するかのみを推定していた。
図にあるようにIntra-Object part location(内部部分の位置)も推定する事で物体の形や向きの特徴を学習することが可能になるらしい。


<h3>3D proposal generation from point cloud</h3>
Anchor BaseまたはAnchor Freeどちらも選べるが、今回はAnchor Baseの方が精度が高いのでAnchor Freeは無視。


<h2>RoI-aware point cloud feature pooling</h2>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482094/4900b162-9792-76f0-1e3b-162d987dd919.png)
[PointRCNN](https://qiita.com/minh33/items/468037a5d244aeedc513)では水色のBounding Boxの領域の点を取り出して、取り出した点を使って3D Refinementを行っていた。そうすると、それぞれの点が推定した提案した3D Bounding Boxの中のどこに位置していたか分からなくなっていしまう。(図には3つのBouding Boxがあるが、どの提案を使っても同じ結果となってしまう)

特徴をVoxelで取り出す事で提案された3D Bounding Boxのどこに点がいたのか情報が残る。


<h2>Stage-II: Part location aggregation for confidence prediction and 3D box refinement</h2>

<h3>Fusion of predicted part locations and semantic part features</h3>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482094/8706793d-964d-00ab-c4c6-c081fcc157f3.png)
Q(part) =>3D Proposalの領域のF1(Intra-object part locationとsemantic segmentation)とx(canonicar coordinateのpoint)を平均した物
Q(sem) =>3D Proposalの領域のF2(semantic feature)とx(canonicar coordinateのpoint)をMax Poolしたもの
Q(roi) => Q(part)のchannel数をQ(sem)のchannel数に合わせて結合したもの


<h3>Sparse convolution for part information aggregation</h3>
14x14x14xChannelのQ(roi)をsparse convolutionを重ねてrefinementの値を推定していく。


<h1>結果</h1>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482094/4c4b2719-570b-29df-3900-f6d410e8e3e0.png)
BackBoneはPointNet++よりSparse Convのほうが精度が高い
ProposalはAnchor FreeよりAnchor Baseのほうが精度が高い

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482094/d5dbf5f1-dfaa-c057-5cec-def3cddfc25d.png)
BEVで比較するとPointRCNNよりPartA2の方が精度が高い事が分かる


<h1>結論</h1>
1. BackBoneをPointNe++から3D Sparse Convに
2. ROI PoolingをROI-aware Poolingに
3. intra-object part locationを推定した事
4. Anchor-FreeからAnchor-Baseへ
これらの変更によってPointRCNNより精度が上がった。

<h1>参考文献</h1>
From Points to Parts: 3D Object Detection from Point Cloud with Part-aware and Part-aggregation Network
https://arxiv.org/pdf/1907.03670.pdf
Redis Pipeliningはネットワークレイテンシの壁を超えるか

# RedisをマルチAZに配置したときの問題
az-1に配置したRedisに対して、az-2に配置したアプリケーションからアクセスすると、az間のネットワークレイテンシの問題で遅くなるという話。

# Redis Pipeliningとは
[Spring Data Redis - 10.13. Pipelining](https://docs.spring.io/spring-data/redis/docs/2.4.2/reference/html/#pipeline)

> Redis provides support for pipelining, which involves sending multiple commands to the server without waiting for the replies and then reading the replies in a single step.

通常は1コマンドごとにサーバに送られるので、大量のデータをRedisとやりとりすると、サーバとの通信も大量に発生することになります。100件のsetで100回通信が発生するのはもちろん、100件のgetでも100回通信が発生します。（setやhashも同様）
Redis RepositoriesのfindByXXとかやると、裏でkeys + レコード数分のhgetallが走るので、通信量が膨大になります。
Pipeliningは、上記の通り複数のコマンドを1度の通信で送るので、ネットワークレイテンシの問題が解消するのでは？という仮説のもと検証をしました。

# 検証内容
## 配置パターン
ローカル、AWS（同一az）、AWS（azまたがり）の3パターンを検証。

### ローカル
ローカルPCのJavaアプリから、ローカルPCのRedisコンテナ on Docker Desktopへアクセスする構成。

ThinkPad X390
redis 6.0.9 （docker run -p 6379:6379 redis）
openjdk version "11.0.2" 2019-01-15
Spring Data Redis 2.4.2

### AWS（同一VPC、同一az）
同一VPC上、同一az内に、Redisサーバ、Javaクライアントのサーバをそれぞれ起動して、同一az内でアクセスする構成。

・Redisサーバ
東京リージョン、ap-northeast-1c
Amazon Linux2
m5.xlarge
Redis 6.0.9

・Javaクライアント
東京リージョン、ap-northeast-1c
Amazon Linux2
t2.micro
openjdk version "11.0.9.1" 2020-11-04 LTS（Coretto）
Spring Data Redis 2.4.2

### AWS（同一VPC、別az）
同一VPC上、異なるaz内に、Redisサーバ、Javaクライアントのサーバをそれぞれ起動して、azまたがりでアクセスする構成。

・Redisサーバ
東京リージョン、ap-northeast-1c
Amazon Linux2
m5.xlarge
Redis 6.0.9

・Javaクライアント
東京リージョン、ap-northeast-1d
Amazon Linux2
t2.micro
openjdk version "11.0.9.1" 2020-11-04 LTS（Coretto）
Spring Data Redis 2.4.2

## 動作検証アプリ
### レコード数
1, 10, 100, 1000の4パターンを検証。
### Redisへのアクセスパターン
setをレコード数分x3回、getをレコード数分x3回やって、set/getそれぞれの平均処理時間[ms]を計測。
setは、"key[n]", "value[n]"
getは、"keys *"した後、それぞれのkeyをgetする
### Redisへのアクセス方法
下記3パターンを検証。
* 1つずつコマンドを発行する方法
* Pipeliningを使ってコマンドを発行する方法
* Parallel Streamを使ってコマンドを発行する方法

# 検証結果
縦軸は処理時間[ms]です。
## ローカル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184997/d54724ac-c352-338b-5855-364787c83e7b.png)
## AWS（同一az）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184997/caacfa7d-9eb9-86f7-7f40-8ad5cd1faedb.png)
## AWS（azまたがり）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184997/f7f37dd3-ce43-6c99-d39a-1ae98637ff2d.png)

# 考察
* Pipeliningは少量のレコードを扱う場合のオーバーヘッドが結構ある（1件の処理でnormalやParallel Steramに比べて倍以上時間がかかっている）。
* azまたがりのケースでは、Pipeliningは有効であるが、100件まではParallel Streamとほとんど変わらなかった。1000件でようやく差が出た。
* Parallel Streamかなり優秀。とはいえ、裏でスレッドを生成して実行する仕組みなので、CPU負荷がかかることに注意する。

# 結論
Redis Pipeliningはネットワークレイテンシの壁を超えるけど、オーバーヘッドもあるので、数百～千件単位のバルク処理にのみ使うのが良い。
Javaの場合、CPU負荷との兼ね合いにもなるが、ほとんどのケースでは、Parallel Steramを使っておけば解決するのではないかと思う。

# 参考
* [Spring Data Redis - 10.13. Pipelining](https://docs.spring.io/spring-data/redis/docs/2.4.2/reference/html/#pipeline)
* [Spring Data Redis（Lettuce）のReadFrom.NEARESTはクラスタ構成でのみ有効な件](https://qiita.com/taka_22/items/b7a419be940b5c49da2b)
* [動作検証アプリのソース](https://github.com/taka2/redis-pipeline-test)
ライブラリにある関数を拡張する試行 (pathlibにcopy関数を追加拡張)

ライブラリにある関数を拡張します。拡張は、Pythonのクラスに、関数を登録するような形。(クラス下の変数に代入する形、毎回そのクラスから生成されるインスタンスは同じ関数を持つようになる)

以下、例として、pathlibの操作方法を拡張したコード。本来はないファイルコピー・フォルダツリーコピーの関数等を拡張追加します。

拡張試行項目:

* **拡張1** : 本来はないファイルサイズ取得のsize関数を拡張追加
  * pathlib.Path("...ファイル...").**size()**
* **拡張2** : 本来はないフォルダコピーのcopy関数を拡張追加
  * pathlib.Path("...フォルダ...").**copy("./test2")**
* **拡張3** : 本来はないフォルダ削除のunlink関数を拡張追加
  * pathlib.Path("...フォルダ...").**unlink()**
* **拡張4** : 本来はないファイルコピーのcopy関数を拡張追加
  * pathlib.Path("...ファイル...").**copy("./test2.txt")**
* **拡張5** : 本来のファイル削除のunlink関数も機能するか確認
  * pathlib.Path("...ファイル...").unlink()
* **拡張6** : 「/」の他に「+」でもパスを連結する拡張を追加
  * pathlib.Path("...フォルダ...") **+ "in1" + "in2" + "test3.txt"**

```python:trial.py

# -*- coding: utf-8 -*-

# ライブラリにある関数を拡張する試行、pathlibの操作方法を拡張してみる

import pathlib
import shutil

# sec: main

def main():
    
    # sec: テストデータ準備
    
    pathlib.Path("./test/in1/in2").mkdir(parents=True, exist_ok=True)
    pathlib.Path("./test/test.txt").write_text("0123456789")
    
    # sec: 拡張1 : 本来はないファイルサイズ取得のsize関数を拡張追加
    
    path_1 = pathlib.Path("./test/test.txt")
    print("size関数を拡張追加:", path_1.size())

    # sec: 拡張2 : 本来はないフォルダコピーのcopy関数を拡張追加
    
    path_1 = pathlib.Path("./test")
    path_2 = path_1.copy("./test2")
    input("フォルダtest2が作成されたか確認: 停止中")

    # sec: 拡張3 : 本来はないフォルダ削除のunlink関数を拡張追加
    
    path_2.unlink()
    input("フォルダtest2が削除されたか確認: 停止中")

    # sec: 拡張4 : 本来はないファイルコピーのcopy関数を拡張追加
    
    path_1 = pathlib.Path("./test/test.txt")
    path_2 = path_1.copy("./test2.txt")
    input("ファイルtest2.txtが作成されたか確認: 停止中")

    # sec: 拡張5 : 本来のファイル削除のunlink関数も機能するか確認
    
    path_2.unlink()
    input("ファイルtest2.txtが削除されたか確認: 停止中")

    # sec: 拡張6 : 「/」の他に「+」でもパスを連結する拡張を追加
    
    path_1 = pathlib.Path("./test/test.txt")
    path_2 = path_1.parent + "in1" + "in2" + "test3.txt"
    print("「+」でもパスを連結:", path_2)

"""コンソール出力例:
size関数を拡張追加: 10
ファイルtest2.txtが作成されたか確認: 停止中
ファイルtest2.txtが削除されたか確認: 停止中
フォルダtest2が作成されたか確認: 停止中
フォルダtest2が削除されたか確認: 停止中
「+」でもパスを連結: test\in1\in2\test3.txt
"""

# sec: 拡張

# ファイルサイズ取得
pathlib.Path.size = lambda self: self.stat().st_size # 拡張追加

# ファイルコピー・フォルダツリーコピー
def pathlib_Path_copy_ex(self, path_to):
    if self.is_file():
        shutil.copy(str(self), str(path_to))
    elif self.is_dir():
        shutil.copytree(str(self), str(path_to))
    return pathlib.Path(path_to)
pathlib.Path.copy = pathlib_Path_copy_ex # 拡張追加

# ファイル削除・フォルダツリー削除
def pathlib_Path_unlink_ex(self, *args, **argkv):
    if self.is_file():
        pathlib_Path_unlink_orig(self, *args, **argkv)
    elif self.is_dir():
        shutil.rmtree(str(self))
pathlib_Path_unlink_orig = pathlib.Path.unlink # 元の関数を保持用
pathlib.Path.unlink = pathlib_Path_unlink_ex # 拡張追加

# 「+」でパスを連結
pathlib.Path.__add__ = lambda a, b: a / b # 拡張追加

# sec: entry

if __name__ == "__main__": main()
```

## Python開発の皆様...

ライブラリpathlibに、このファイルコピー・フォルダツリーコピーを行う`copy`関数が標準であると便利かなと思っております...

* **拡張2** : 本来はないフォルダコピーのcopy関数を拡張追加
  * pathlib.Path("...フォルダ...").**copy("./test2")**
* **拡張4** : 本来はないファイルコピーのcopy関数を拡張追加
  * pathlib.Path("...ファイル...").**copy("./test2.txt")**
第十回：再帰



# 目標

フィボナッチ数列を求めるコードの作成を行う．

フィボナッチ数列とは，n項=(n-1)項+(n-2)項となるもの．0,1,1,2,3,5,8,13,21 &#x2026;となる．


# n=0の時

0項目は0となるので，以下のようにコーディングする．

```ruby
require './assert_equal.rb'
def fib(n)
  if n==0
     return 0
  end
end

puts assert_equal(0,fib(0))
```

```ruby
require './assert_equal.rb'
```

によって，前回作成したassert\_equal.rbを呼び出している．

これを実行すると，

```
$ ruby fibonacci.rb
true
```

となる．


# n=1の時

1項目は1となるので，以下のように追加する．

```ruby
require './assert_equal.rb'
def fib(n)
  if n==0
     return 0
  end
  if n==1
     return 1
  end
end

puts assert_equal(0,fib(0))
puts assert_equal(1,fib(1))
```

これを実行すると以下のようになる．

```
$ ruby fibonacci.rb
true

true

```


# n=2の時

2項目は1となるので，以下のように変更する．

```ruby
require './assert_equal.rb'
def fib(n)
  if n==0
     return 0
  end
  if n<=2
     return 1
  end
end

puts assert_equal(0,fib(0))
puts assert_equal(1,fib(1))
puts assert_equal(1,fib(2))
```

コードが長くなってきたので，if文を短くして，puts assert\_equalの部分を配列にする．

```ruby
requrire './assert_equal.rb'
def fib(n)
  return 0 if n==0
  return 1 if n<=2
end

[[0,0],[1,1],[2,1]].each do |index, expected|
  puts assert_equal(expected, fib(index))
```


# n=3以降の時

3項目以降は，2項目以前が値が決まっているので，以下のように変更する．

```ruby
requrire './assert_equal.rb'
def fib(n)
  return 0 if n==0
  return 1 if n==1
  return fib(n-1) + fib(n-2)
end

[[0,0],[1,1],[2,1]].each do |index, expected|
  puts assert_equal(expected, fib(index))
```

2項目も0項目+1項目で求められるので，n<=2をn==1に変更してある．これでフィボナッチ数列を求められる．


# 参考サイト

参考にしたサイトは以下の通り．

-   <https://qiita.com/daddygongon/items/1bb27b8920c201d5c520>



------
 - **source** ~/grad_members_20f/members/yuhsuzu/r10.org
[TypeScript] ユニオンをタグ付けする


「プログラミングTypeScript 第6章 高度な型 p134 」の内容


## 合併型なユニオン

`UnionObj`は、２つのObjectが混ざった形のオブジェクトでも許容する仕様。

```ts
type Obj1 = {id: string, value: string};
type Obj2 = {id: string, name: string};
type UnionObj = Obj1 | Obj2;

const func = (obj: UnionObj) => {
  // ...
}

func({id: '1', value: 'pen'               ) // <= OK
func({id: '2',               name: 'taro'}) // <= OK
func({id: '3', value: 'pen', name: 'taro'}) // <= これもOK!!!
```

## タグ付けされたユニオン

タグとして`{type: string}`を作ってあげることで、明確に`Obj1`/`Obj2`のどちらなのかを切り割けられる

```ts
type Obj1 = {type: 'Obj1', id: string, value: string};
type Obj2 = {type: 'Obj2', id: string, name: string};
type UnionObj = Obj1 | Obj2

const func = (obj: UnionObj) => {
  // ...
}

func({type: 'Obj1', id: '1', value: 'pen'              }) // OK
func({type: 'Obj1', id: '2',               name: 'taro'}) // NG
func({type: 'Obj1', id: '3', value: 'pen', name: 'taro'}) // NG
```

### メモ：モック

上記の`func`は下記に差し替えてあげれば動きを確認できる

```ts
const func = (obj: UnionObj) => {
    Object
      .keys(obj)
      .forEach((key) => console.log(obj[key as keyof typeof obj]))
}
```

### おわり
Ubuntu 18.04 LTS+WINE+winetricksでdotnetをインストールするときの落とし穴

# 要約

* winetricks は最新版を wget でインストール（Ubuntu 18.04 LTS用パッケージの winetricks は古い）。
* cabextract パッケージを追加インストール。

# 背景

* Ubuntu 18.04 LTS + WINE で .NET Framework4.6.2 用のアプリケーション実行環境を構築することになった。
* ネットで調べて試行錯誤しながら成功したので、スクリプト化しておくことにした。
* しかし、Ubuntu Desktop 18.04.5 LTS を新規インストールした状態からだと、winetricks による dotnet462 環境構築で失敗してしまう。

# 問題現象

## (1) 403 Forbidden

winetricks で dotnet462 以降をインストールしようとすると、インストーラのダウンロードに失敗する。

```
$ winetricks dotnet462
...

HTTP による接続要求を送信しました、応答を待っています... 403 FORBIDDEN
2020-12-30 01:18:59 エラー 403: FORBIDDEN。

------------------------------------------------------
Downloading https://web.archive.org/web/2000/https://www.dropbox.com/s/1bdocgbate8hc8z/dotNetFx462_Full_x86_x64_Slim.exe?dl=1 failed
------------------------------------------------------
```

## (2) dotnet462 の DLL がインストールされずに終了

winetricks で dotnet462 以降をインストールしようとすると、エラー表示なく終了するものの dll がインストールされない。

```
$ export WINEARCH=win32
$ export WINEPREFIX=$HOME/winedotnet
$ winetricks -q dotnet462
...

$ winetricks list-installed
Executing mkdir -p /home/username
Using winetricks 20201206-next - sha256sum: 0feed209ffbf9d38dd1dd0ed93f9303c12a585077c6efe35ea7525d1004b3b8b with wine-5.0.3 and WINEARCH=win32
remove_mono
remove_mono
remove_mono
```

# 対処方法

## (1) winetricks の最新版をインストール

次の公式ドキュメントに従って、最新版の winetricks をインストールする。

> https://wiki.winehq.org/Winetricks#Getting_winetricks
> https://appdb.winehq.org/objectManager.php?sClass=version&iId=34702

```
sudo wget 'https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks' -O /usr/local/bin/winetricks
sudo chmod +x /usr/local/bin/winetricks
```

apt でパッケージをインストールしてしまった場合は、アンインストールしておく。`sudo apt remove -y winetricks`

## (2) cabextract パッケージをインストール

apt で cabextract パッケージをインストールしておく。

```
sudo apt install -y cabextract
````

> https://kakurasan.blogspot.com/2015/07/winetricks-features-usage.html#h-winetricks-features-usage-1-2

ちなみに、apt パッケージ版の winetricks では `p7zip` も追加インストールされる。

> $ apt show winetricks | grep -i depends
> Depends: binutils, cabextract, p7zip, unzip, wget | curl, wine

# 参考スクリプト

```
# winetricks
sudo wget -O /usr/local/bin/winetricks 'https://raw.githubusercontent.com/Winetr
icks/winetricks/master/src/winetricks'
sudo chmod +x /usr/local/bin/winetricks

# cabextract
sudo apt install -y cabextract

# dotnet462
export WINEARCH=win32
export WINEPREFIX=$HOME/winedotnet
export WINEDLLOVERRIDES="mscoree,mshtml="
wineboot --init
winetricks -q --force dotnet462 corefonts cjkfonts

```
とりあえずブログを作ってみる。備忘録

ruby on rails勉強中。

いろいろ調べながらやっていたら混乱するので、
あたまの整理目的で使用しています。

間違い等あればコメントしてくれたらうれしいです(^^♪

ヘッダーを作る①
application.html.erbに記述。
このかたまりは記述の必要がある。

<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
    <%= csrf_meta_tags %>

<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
CSSの読み込み
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
jave scriptの読み込み

<%= csrf_meta_tags %>
安全性を保つためのもの
https://railsguides.jp/security.html#%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B5%E3%82%A4%E3%83%88%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%AA-csrf
Railsのvalidateを自作する

# はじめに
[前回の続き](https://qiita.com/smbbc20/items/952a33501343614f197f)で「Ruby on Rails 5　速習実践ガイド」のアウトプットを行うための投稿です。
今回は、モデルのバリデートを自作する方法を書いていきます！例として、Taskモデルのnameカラムに、ハイフンが含まれないようなvalidateを作成します。

# 目次
<!-- タイトルとアンカー名を編集 -->
1. [自作validateのメソッドを作成](#Chapter1)
1. [自作validateを呼び出す](#Chapter2)
1. [エラーメッセージを表示する](#Chapter3)
1. [参考文献](#reference)

<!-- 各チャプター -->
<a id="#Chapter1"></a>
# 自作validateのメソッドを作成

```task.rb
private
def validate_not_including_hyphen
  errors.add(:name, 'にハイフンを入れないでください！') if name&.include?('-')
```

このコードでは、nameがnilではなくハイフンを含んでいるときに、errorを追加する記述です。

<a id="#Chapter2"></a>
# 自作validateを呼び出す

```task.rb
validate :validate_not_including_hyphen
```

また、コールバック（before_createなど）で呼び出すこともできます。

<a id="Chapter3"></a>
# エラーメッセージを表示する
```
- @task.errors.full_message.each do |message|
      li = message
```

# 参考文献
- [現場で使える Ruby on Rails 5速習実践ガイド](https://www.amazon.co.jp/%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B-Ruby-Rails-5%E9%80%9F%E7%BF%92%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89-%E5%A4%A7%E5%A0%B4-ebook/dp/B07JHQ9B5T)
Qiita事始め

初めてのQiita。
来年から勉強したことを記録する。

まずはqiitaの使いかたから学ぶ。
___

文字の装飾

**アスタリスク２つで太字**
__アンダーバー２つで太字__
*アスタリスク１つでイタリック体abc*
_アンダーバー１つでイタリック体abc_
~~打ち消し~~
# これはH1タグです
## これはH2タグです
###### これはH6タグです


```python:first.py
print('first Qiita')
```

___
まずはこれくらい知った上で、はじめて見ます。
ONNXに埋め込むopsetを設定する

## ONNXのopsetとは
ONNXファイルには、ファイル作成時のバージョンとしてopsetを指定する機能があります。opsetの値に応じて、使用できるオペレータやその機能（オペレータのプロパティ）が変わります。特に指定が無い場合は最新のopsetとして扱います。

Deep Learning系のツールによってはONNXファイルのopsetの値を確認し、自分自身が処理できるかどうかを確認するルーチンが含まれています。そこの確認ルーチンではじかれた場合は、opsetを下げて再度ONNXファイルを作る必要があります。


https://github.com/onnx/onnx/blob/master/docs/Operators.md

ここを見るとそれぞれのオペレータが対応しているバージョンを確認出来ます。例えば、ABSではバージョンとして1,6,13が存在し、13ではABSで使える型としてbfloat16が追加されていることが分かります。


## Kerasでopsetを指定してONNXファイルを作る
opsetの指定方法はコンバーターによって違いますが、大抵は引数にopsetのバージョンを指定できます。
keras2onnxを使う場合の指定方法です。

```python
import tensorflow
import onnx
import keras2onnx

model_file = 'foo.h5'

#opsetを設定して保存
keras_model = tensorflow.keras.models.load_model(model_file)
onnx_model = keras2onnx.convert_keras(keras_model, 'foo1', target_opset=9)
```

target_opsetを指定することで、opset=9のONNXファイルが生成されます。

## 直接ONNXファイルを修正する
使われているオペレータが明らかに古いオペレータと互換性があることが分かっている場合（新しい機能を使っていない場合）、単にコンバータのチェックを通すためだけにONNXファイルのopsetの値を指定したい時もあります。この場合はONNXファイルを直接編集してopsetを変更することも出来ます。

上のソースの続きとして

```python
onnx_model.opset_import[0].version = 10
with open("op10.onnx", 'wb') as f:
    f.write(onnx_model.SerializeToString())
```

onnxファイルのopset_import[0].versionに直接値を指定しています。保存されるときには10がopsetとして使われます。


## opsetの確認方法

てっとり早い方法としてはprint(onnx_model)で直接onnxを文字列として出力すると、最後の方にopset_importとして値が表示されます。

```xml
opset_import {
  domain: ""
  version: 10
}
```

opsetは10に設定されています。
配列について

前回は、C#の変数についてまとめました。
今回は、配列です！！
分かりやすいので、int型の配列を例として挙げます。
![スクリーンショット 2020-12-30 081820.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/909788/e791dc52-141a-a8bc-dcd6-f7d2f7e3d720.png)


変数を列としてまとめたものが配列です。
配列がないと・・・
名前を付ける回数が増えて面倒くさい！
そしてアクセスも面倒くさい！

たとえば、1から3までの連続した値を扱いたいな～と思ったとします。
配列がないと、
int number1=1;
int number2=2;
.
.
.

としないといけません。面倒です。
が、配列だと１行で済みます！
![スクリーンショット 2020-12-30 083820.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/909788/e812322a-a26b-f73a-4735-a7e305ab66b9.png)
ちなみに[]の中の数を配列のインデックスと言い、{}の中の値を要素と言います。
「numberの要素数はいくつ？」と聞かれたら、３つあるので「３」


ではVisualStudioで表示してみましょう！
![スクリーンショット 2020-12-30 090740.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/909788/1b689e5e-445e-62cd-4ecf-ffde104831e6.png)

なぜでしょう？
実行すると「ハンドルされていない例外とあります」
要素数は３個なんですからこう書けば、１，２，３と表示してくれそうなのに？？

実は、配列やリストなど連続しているものは0から始まるのです！！
ですから、ここでも0から書かないといけませんでした。
「ハンドルされていない例外」とは、「え～３番目には何もないよ！！」と言っているわけです。
![スクリーンショット 2020-12-30 093203.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/909788/a4db8e84-f954-f6ed-61ae-4118e4840c30.png)

連続してるんだから、for分と相性が良さそうですね？
では試してみましょう！！
![スクリーンショット 2020-12-30 094249.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/909788/8f92810c-510e-7274-6bee-76dbd9ceadd9.png)
しかも、配列ということは連続して値が代入されているので並べ替えも楽々です！
例えば降順にしたい！！!
そんな時は
ラムダ式を使って楽々降順！！

![ラムダ式結果あり2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/909788/331e1d98-50e9-ff14-1a9f-fe260c20ef7c.png)


上の図の黄色い線の部分はラムダ式といいます。
後で説明するから、「降順にしたいときはこうなんだな～」くらいに覚えておいてください。


では、次は昇順にしてみます。
![array_sort結果あり.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/909788/044748b1-7123-75d8-0cee-9218276d2b15.png)

更に配列を使う上で、注意してほしいところがあります。
 int [] number = new int [ 3 ]{ 1,2,3};
それは [ 3 ]　の中の  3 は無くても同じ動きをしてくれます。
なぜなら、numberの中に入れたいのは何か(int型)で入れたい要素の数(今回は3つ)とわかっているからです。
なので、消しましょう！！
では、もし { 1,2,3};がなく、ただ
int [] number = new int [ 3 ]；としたいときに、要素の数をあらわす3がないとどうでしょうか？
これはエラーになります。
![配列のNG.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/909788/5fe9c945-c940-f752-2ae1-db8106e8e012.png)

VisualStudioでエラーを見てみると、CS1586:配列を作成するには、配列のサイズまたは配列の初期化子を指定する必要があります。と出ます。
まさしく、要素を入れるか、ずばり要素の数を言ってねとということです。
更に[3]の中を直値ではなく変数にしておくと、もし後々「変数の数を変えたいな」と思ったときに、変更箇所が１つ(下のまとめのint index=3のところの3の数を変えるだけ)で済むというメリットがあります。

![配列のまとめ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/909788/8c330c14-54b2-f84a-e859-4cff06ba1324.png)