試行錯誤ダイアリー

新卒エンジニアが日々の技術的な学び,働き方,日々感じたこと等を書きます

【Google Apps Script】スプレッドシートと連携時の日付の引き算について

先日google apps scriptでスプレッドシートと連携させたタスクシートを作りました.

記事:スプレッドシートとGoogle Apps Scriptでタスク管理 - 試行錯誤ダイアリー

そのとき,スプレッドシートにおける日付の引き算をしたときのフォーマットの違いに困ったのでまとめました.

スプレッドシートでの日付

スプレッドシートでは,セルを右クリックして一番したまでスクロールすると
「データの入力規則」という項目があり

f:id:appli-in:20180619005743p:plain:w400

こちらをクリックして,出てきたダイアログの条件を日付にすると,

f:id:appli-in:20180619010118p:plain:w450

セルをクリックするだけでカレンダーが表示され,日付を選択して入力することができます.

f:id:appli-in:20180619010543p:plain:w300

こちらの記事によると
参考記事:Spreadsheetでの日付と、Google Apps ScriptでのnewDate()

スプレッドシートでは時刻の基準が1899年12月30日 00:00:00になっているようです. 確認できなかった.

google apps scriptでの日付

google apps scriptでは,言語がほぼjavascriptのため,日付を扱うときはDateクラスを使用します.
Class DateField  |  Apps Script  |  Google Developers

Dateクラスはこのような仕様のようです.

Date オブジェクトは、1970 年 1 月 1 日 (UTC) から始まるミリ秒単位の時刻値を基準としています。

引用:Date - JavaScript | MDN

実際に試してみると

function test(){
  var date = new Date(1970, 0, 1);//1970年1月1日
  Browser.msgBox(date3.getTime());
}

f:id:appli-in:20180619015408p:plain:w500

となりました,これは日本時間で+9時間されているためです(32400秒は9時間).

Dateオブジェクトでは以下のようにいくつかのフォーマットでインスタンスを生成できます(こちらは一部で他にもあります).

function test(){
  var date1 = new Date();//現在の時刻(時分もはいります)
  var date2 = new Date('2018/6/16');//2018年6月16日
  var date3 = new Date(2000, 3, 15);//2000年4月15日
  
  Browser.msgBox("date1 :" +date1+"\\n"+"date2 :" +date2+"\\n"+"date3 :" +date3);
}

出力はこんな感じ

f:id:appli-in:20180619012814p:plain:w500

ここで気をつけてほしいのは,new Date();だと,完全に現在の時刻で時分も入ってしまっていることと,new Date(2000, 3, 15)のフォーマットで書いたときは,月が0から始まることです.ここでは"3"にしているので4月の"Apr"になっていることです.

問題点

それぞれを引き算すると思わぬエラーが発生してしまうことがあります.というのも,google apps scriptではnew Date();は時分も入力されますが,スプレッドシートでは日付だけなので,引き算すると一日単位の値が返ってきません.なので,日にちに関わる処理を書くときは気をつけなければなりません.

実際に試してみます

使うスプレッドシートはこれです.1行3列目に=today()で今日の日付を入力してあります.

f:id:appli-in:20180619020656p:plain:w700

それぞれgoogle apps scriptで取得したtodayと9行5列目の6月18日の引き算.スプレッドシートので取得したtodayと9行5列目の6月18日の引き算をしています.

function test(){
  Browser.msgBox("GAS-スプレッドシート:"+(today - sheet.getRange(9,5).getValue())+"\\n"+"スプレッドシート-スプレッドシート:"+(sheet.getRange(1,3).getValue() - sheet.getRange(9,5).getValue()));
}

結果はこちらです.

f:id:appli-in:20180619021022p:plain:w500

出力される値が一日単位のものではなく中途半端な値になっています.

スプレッドシートの=today()で取得した数値は時分のない日付のみの値になっているため,引き算をすると一日のミリ秒である86400000が計算結果として出ます.

まとめ

google apps scriptとスプレッドシートの日付の説明をして,現在の日付を取得する際のフォーマットの違いについて,実際に数値を出力して見ることで確認しました.

google apps scriptとスプレッドシートを連携させて,今日の日付を取得し算出した値同士を比較する際は十分気をつけなければいけません.

スプレッドシートとGoogle Apps Scriptでタスク管理 - 試行錯誤ダイアリー のタスク管理シートでは,今日の日付をgoogle apps scriptで取得せず,スプレッドシートの方で取得することでこの問題を回避しています.

【Google Apps Script】スプレッドシート連携してタスク管理

今の部署では,それぞれのタスクをグーグルのスプレッドシートで管理していて,毎週の会議でそれを参照して進捗管理しているのですが,記載しているタスクの多さと,やったタスクが複数の案件をまたがったりしていて分かりづらいです.なので,ボタンを押したらその週の完了したタスクなどが表示されたら楽だなーと思ったのでやってみました.

実現したいこと

ボタンを押したら

  • 今週完了したタスク
  • 進捗中のタスク
  • 締切の近いタスク

の3つの項目についてダイアログにこのように表示したいと思います.

f:id:appli-in:20180618012444p:plain:w400

スプレッドシートの項目とイメージ

作ったスプレッドシートはこんな感じです.

f:id:appli-in:20180618013543p:plain

左上の青い「今週の進捗」と書かれた図形がボタンです. 残日数は”期限日”から”今日の日付”を引いた日数になっているので,すでに期限の過ぎたタスクについては"マイナス表示"になります.

また,開始日,期限日,完了日については,データの入力規則で日付に変更してあります.

実装

先に実装したコードを紹介して,その詳細について書いていきたいと思います.

コード

var sheet=SpreadsheetApp.getActiveSheet();//シートの情報を取得
var completed = "";//完了のタスク
var ongoing = "";//進捗中のタスク
var deadline = "";//締切が近いタスク
var day = 86400000; //一日をミリ秒(ms)換算した値

function checkTask(){
  //表を上から見ていってそれぞれ進捗中,完了にわけて文字列に追加.
  for (var i=3; i <= sheet.getLastRow(); i++){
    //完了したタスク(先週の報告から)
    if(sheet.getRange(i,8).getValue()=="完了" && (sheet.getRange(1,3).getValue() - sheet.getRange(i,5).getValue()) <= day * 7){
      completed = completed + sheet.getRange(i,2).getValue() + "\\n";
    }
    //進捗中のタスク
    if(sheet.getRange(i,8).getValue()!="完了" && (sheet.getRange(i,3).getValue() != null || sheet.getRange(i,3).getValue() != "")){
      ongoing = ongoing + sheet.getRange(i,2).getValue() + " : " + sheet.getRange(i,9).getValue() + "\\n";
    }
    //締切が近いタスク(2週間以内に締め切りがある)
    if(sheet.getRange(i,7).getValue() < 14 && sheet.getRange(i,8).getValue() != "完了"){
      deadline = deadline + sheet.getRange(i,2).getValue() + " : " + sheet.getRange(i,9).getValue() + "\\n";
    }
  }
}

function showProgress(){
  checkTask();
  Browser.msgBox("【完了したタスク】\\n"+ completed + "【進捗中のタスク】\\n" + ongoing + "【締切が近いタスク】\\n" + deadline);
}

タスクの区分判定

今回はタスクを「今週完了したタスク」,「進捗中のタスク」,「締切が近いタスク」にわけます.

今週完了したタスク

今週完了したタスクの定義はステータスが”完了”になっていることと,今週中に完了したことなので,条件文を以下のようにしました.

    //完了したタスク(先週の報告から)
    if(sheet.getRange(i,8).getValue()=="完了" && (sheet.getRange(1,3).getValue() - sheet.getRange(i,5).getValue()) <= day * 7){
      completed = completed + sheet.getRange(i,2).getValue() + "\\n";
    }

この関数では事前に定義したcompletedの文字列に,条件を満たしたタスク名を文字列として足していく処理をしています.これ以降の他のタスクの区分でも同じ事をしています.

if分の中のsheet.getRange(1,3).getValue()は常にセルで=today()によって今日の日付を表示しているセルを表してます.

ここで注意したいは,スプレッドシート上でセルでtoday関数=today()を使って表示した日時とセルの入力規則で表示した日時を単純に引き算するとミリ秒で返ってきます(セルの入力規則で日時にしたセル同士の引き算では日数が返ってくる),なので,事前に一日のミリ秒を定義しそれを日数分かけたものと比較することで,日数の基準を計算しています.この条件を設定しないと一週間以上前に完了したタスクまで表示されてしまい大変なことになります.

関連記事:【google apps script】スプレッドシートと連携時の日付の引き算について - 試行錯誤ダイアリー

またgoogle apps script(javascriptも?)ではダイアログ中の改行は"\n"ではなく"\\n"なので注意します.

進捗中のタスク

進捗中のタスクでは,ステータスが完了ではないもので,タスクに取り掛かっている状態,つまり,開始日が"null"でもなく""(空白)でもないタスクを加えていきます.

    //進捗中のタスク
    if(sheet.getRange(i,8).getValue()!="完了" && (sheet.getRange(i,3).getValue() != null || sheet.getRange(i,3).getValue() != "")){
      ongoing = ongoing + sheet.getRange(i,2).getValue() + " : " + sheet.getRange(i,9).getValue() + "\\n";
    }

どうやらスプレッドシートでは,一度も文字を入力してないセルは"null"と判定されますが一度なにかの文字や数値が入ったセルは消したあとは""(空白)と判定されてしまうようなので注意してください.

締切が近いタスク

こちらは簡単で純粋に"期限日"から"今日の日付"を引いた”残日数”が14日以内のものを集計して表示します.

    //締切が近いタスク(2週間以内に締め切りがある)
    if(sheet.getRange(i,7).getValue() < 14 && sheet.getRange(i,8).getValue() != "完了"){
      deadline = deadline + sheet.getRange(i,2).getValue() + " : " + sheet.getRange(i,9).getValue() + "\\n";
    }

ここではセルの入力規則での日時同士なので日数の「14」で判定します.

ボタンの設置

タブメニューの"挿入"->"図形描写"から適当に図形を生成します. そして,生成した図形を右クリックすると図形の右上にメニューを開けそうなボタンが出てきます.そこをクリックすると,「スクリプト割り当て」というメニューがあるのでクリックしてスクリプト上の関数名を入力します.今回の例だとshowProgressです.括弧は入力する必要はありません.

これだけで図形をクリックすると指定した関数を実行することができます.

showProgress関数では,checkTask関数を実行してそれぞれの文字列変数にタスク名を追加させた上で,ダイアログ出力(Browser.msgBox())しています.

function showProgress(){
  checkTask();
  Browser.msgBox("【完了したタスク】\\n"+ completed + "【進捗中のタスク】\\n" + ongoing + "【締切が近いタスク】\\n" + deadline);
}

まとめ

スプレッドシートでタスク管理をして,一週間のタスク状況を出力するコードをgoogle apps scriptで書きました. これで,自分の完了したタスクを少し管理しやすくなったんじゃないでしょうか.

大変だった点としては,today関数の内部の数値とスプレッドシートの値の差分がミリ秒で返ってくることで,事前に一日のミリ秒を定義しなければいけない所で少しつまづきました.

詳しくはこちらの記事で,

blog.trial-and-error-diary.fun

【Windows10】PythonでのopenCV導入方法

画像認識をやりたくて,Windows10のPython(Anaconda)でopenCVを導入したときの方法と出たエラーと対策.

openCVでこんな事してみました.
【OpenCV】画像の色抽出 - 試行錯誤ダイアリー

環境

  • Windows10
  • Python3.6.1(Anaconda 4.4.0)

導入方法

まずopenCVをダウンロードしましょう
下記リンクから
自分のバージョンにあったファイルをダウンロード
https://www.lfd.uci.edu/~gohlke/pythonlibs/#opencv

バージョンはコンソール上でpython -Vで確認下図の様なものがでてきます.

f:id:appli-in:20180212175703p:plain

ここでは,python 3.6.1で64bitなので
opencv_python‑3.4.0‑cp36‑cp36m‑win_amd64.whlを選択.

cp◯◯がpythonのバージョン
win_◯◯がOSのbit数(64の場合はamd64)

次にAnaconda promptを開きます.windonwsメニューから実行できます(下図赤枠部)
f:id:appli-in:20180212172833p:plain

openCVのwhlファイルをダウンロードしたディレクトリに移動して
pip install opencv_python‑3.4.0‑cp36‑cp36m‑win_amd64.whl を実行

エラー

ImportError: No module named cv2

導入せずにimport cv2するとImportError: No module named cv2というエラーが出ます. 最初に何も考えずにAnacondaに入ってると思ってました. 原因:そんなもの入ってません(ダウンロードしましょう)

opencv_python-X.X.X-cpXX-cpXXm-win_amd64.whl is not a supported wheel on this platform

※Xは数字が入ります

原因:ダウンロードしたopenCVのバージョンが一致していない.
(python 3.6なのにopencv_python-3.4.0-cp37...をダウンロードしていたり)

python -Vでバージョンを確認して適切なバージョンのものをダウンロードする.

ImportError: numpy.core.multiarray failed to import

ダウンロード完了後にimport cv2したときのエラー

原因:numpyのバージョンが合ってません

Anaconda promptでpip install numpy --upgradeでバージョンを最新にすると解決

【openCV】画像の色抽出

OpenCVで色検知の方法を調べると,特定の色を検知するものは多いが,色の抽出をするものはそんなに多くないように感じた.

今回は画像をインプットしたときにその画像が大体何色なのかを判別したい.各ピクセルの平均を取れば結果としては出力できるが,求めた結果とは異なることが多い.今回はOpenCVを使って画像をHSV形式に変換した後,ピクセルのHue値のヒストグラムで可視化することで画像が直感的に何色なのか調べた.

openCV導入方法はこちら
関連記事:【Windows10】PythonでのopenCV導入方法 - 試行錯誤ダイアリー

色識別方法

openCV で画像を読み込んで, HSV方式に変換して検出する.

色検知に関してはRGB方式ではなくHSV方式でするのが一般的らしい.
RGBでは一つの色を表現するのにR,G,Bそれぞれのパラメータを細かく設定する必要があるが,
HSVではHue(色相)で色を調節できるので都合がいいらしい.
※HSV = Hue(色相),Saturation(彩度),Value(明度)

opneCVではそれぞれの値の範囲が
Hue(色相) : 0 ~ 180
Saturation(彩度) : 0 ~ 255
Value(明度) : 0 ~ 255
で表現される.

特に色相はこんな感じ(左が0で右は180)
f:id:appli-in:20180218183137p:plain

画像

今回用いる画像は以下の2つ.理由は特定の色が圧倒的に強い画像とHueの色空間をまんべんなく網羅した画像を比較することで,ヒストグラムによる可視化の効果を確認するため.

tomatoRGB
用いた画像(左:トマトの画像, 右:RGBの画像)

手順

まず画像を読み込みHSVに変換

img_scr = cv2.imread("picture_name.png")
img_hsv = cv2.cvtColor(img_scr, cv2.COLOR_BGR2HSV)

※openCVでは画像をimreadで読み込んだときはRGBではなくBGRとして読み込まれている.

画像のサイズを取得し,検出した色を格納する配列を用意

height = img_hsv.shape[0]
width = img_hsv.shape[1]
pixels = []

各ピクセルを調べてある閾値を満たしたらpixels[]に格納する.
ここでは白色や暗い色を検知したくないので , Saturation(彩度)を45以上
Value(明度)を32以上とした.

for y in range(height):
    for x in range(width):
        if (img_hsv[y, x, 1] > 45 and 32 < img_hsv[y, x, 2]):
            pixels.append(img_hsv[y, x, 0])

今回は色相を12分割して色を判断する.
ヒストグラムの出力

plt.xlim(0,180) # x軸の範囲
plt.xticks([0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180]) # x軸の目盛りの設定
plt.hist(pixels, bins=12, range=(0,180)) # ヒストグラムの出力 

その後最頻値を求めて その画像の色を判断する.

# 軸の作成 (0から180を13等分[12個の区間を出したいため]) 
bins = np.linspace(0, 180, 13)  

# pixelsをそれぞれの要素を12分割した色範囲に格納  
index = np.digitize(pixels,bins) 

# 最頻値を求めるためにCounterを使う
c = Counter(index)
print(c)
mode = c.most_common(1)

# 最頻値の出力
print(mode[0][0])

結果

トマトの画像

f:id:appli-in:20180218185351p:plain:w600
画像1の結果
Counter({1: 188904, 2: 4436, 3: 3005, 12: 2650, 4: 570, 5: 106, 6: 4})
最頻値 : 1

ちゃんとトマトの赤色が一番多い結果になっている.

RGBの画像

f:id:appli-in:20180218185445p:plain:w600
画像2の結果
Counter({1: 5373, 5: 5372, 9: 5368, 11: 1998, 3: 1996, 7: 1996, 2: 39, 10: 39, 6: 37, 4: 36, 8: 34, 12: 30})
最頻値 : 1

RGBのあたりがそれぞれ一番多い結果になっている.

まとめ

OpenCVで画像がどんな色なのかを抽出するために,HSV形式に変換して,結果的にヒストグラムにすることで色を判別した.結果的に画像がどんな色で構成されているかをヒストグラムで可視化することができた.今回はやっていないが,基本的に色を抽出したい物体は画像の中心に位置することが多いので,中心付近のピクセルからのみ抽出すれば,範囲を限定できるのでより効果的かもしれない.

ソースコード

# -*- coding:utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
import cv2
from collections import Counter

# 画像を読み込み
img_scr = cv2.imread("tomato.jpg")

# BGR->HSV に変換
img_hsv = cv2.cvtColor(img_scr, cv2.COLOR_BGR2HSV)

height = img_hsv.shape[0]
width = img_hsv.shape[1]

pixels = []

for y in range(height):
    for x in range(width):
        if (img_hsv[y, x, 1] > 45 and 32 < img_hsv[y, x, 2]):
            pixels.append(img_hsv[y, x, 0])

plt.xlim(0,180)
plt.xticks([0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180])
plt.hist(pixels, bins=12, range=(0,180), align="mid",rwidth=1.0,color="blue")

bins = np.linspace(0, 180, 13)
print(bins)
index = np.digitize(pixels,bins)
c = Counter(index)
print (c)
mode = c.most_common(1)
print(mode[0][0])

【Python】たまにやらかすエラーと対処法

未だにたまにやらかすPythonのエラー,その対処法.

TypeError: ufunc 'subtruct' did not contain loop with signature matching types

型の違いでsubtructつまり引くことができないっぽい

int とfloatの引き算のやっていたのでintをfloatにキャスト(使い方間違ってるかも?)して演算したいって思ってリストの要素を

(float)a[n]

こんな風にしたんだけど怒られた pythonのキャストは

float(a[n])

いろんな言語使ってるとたまにごっちゃになるよね

Error: sequence expected

csvファイルに数値を書き込みしようとしたら怒られた

_csv.Error: sequence expected

シーケンスじゃないとダメらしい,たぶん入力が配列の形式でないとダメってことか

writer.writerow(num)

writer.writerow([num])

にしたらOKだった.

【swift4】センサー値取得するiOSアプリ作ってみた

作ってみたいアプリができたので,iOSアプリの作り方を勉強. とりあえず各種センサーの値を取得して表示するアプリを作成してみた.

センサー値の取得が知りたいという方は 目次の”センサー値を取得してみる”から

環境構築

iOSアプリを作成するためにはXcodeをインストールする. Xcode - Apple Developer こちらからダウンロードしてインストール.
2018年6月4日現在ではHighSierra以上のOSが必要.Sierra以前のOSを使っている人はアップグレード.

Xcodeを起動する

Xcodeを起動すると以下のような画面が出てきます.

f:id:appli-in:20180604010057p:plain:w600
Xcode起動時のウィンドウ

  • Get started with a playground
    playgroundはプログラムの動作確認するための環境.Pythonとかの対話モードみたいな感じですね.
  • Create a new Xcode project
    Xcodeのプロジェクトを作成.アプリを作成するときはこれを選択. 
  • Clone an existing project
    Githubとかにあるプロジェクトをクローンするときはこれ

Create a new Xcode project を選択.

そうしたらこんなウィンドウがでてくる. 項目はそれぞれ以下の表の通りで.適当な値を入力. f:id:appli-in:20180608192417p:plain:w600

項目 意味
Product Name プロジェクト名
Team 開発アカウント名(今回は入力なし)
Organization Name 開発する組織(今回は自分の名前を入力)
Organization Identifire 組織の識別子
Language 言語(今回はSwiftを選択)
Use Core Data データベースの使用の有無
Include Unit Test 単体テストを利用するか
Include UI Test UIテストを利用するか

実装

部品を配置

Nextを押すと開発ウィンドウがでてくる. すでにそれっぽい.

f:id:appli-in:20180608194312p:plain:w600

試しに,テキストフィールドを追加してみる. 下図の赤枠部分を選択し"Label"をドラッグしiPhoneの画面っぽいところにドロップする.

f:id:appli-in:20180608195007p:plain:w600

そうすると,テキストボックスが追加された(TEXTの文字は自分で入力した).ボックスをダブルクリックすると入力モードに切り替わる.

f:id:appli-in:20180608195328p:plain:w300

すごい...

今までGUIアプリって”Labelオブジェクト生成して,〇〇レイアウトで...”みたいな感じで面倒だし,よくわからんけどレイアウトが思い通りにいかないことが多いイメージだったけど,これはめっちゃ簡単だ.位置もドラッグして動かせる.

とりあえず完成形のイメージでレイアウトで実装してみる. 下の"センサー値取得”のボタンを押したら,光センサーの値と加速度センサーの値をその都度表示させる.

f:id:appli-in:20180608202613p:plain:w300

表示させてみる

ウィンドウ画面右上の赤枠のボタンをクリック. f:id:appli-in:20180608203358p:plain

するとエディタがでてくる.

先程作ったラベルをcontrolキーを押しながらエディタにドラッグしようとすると青い線が出る. その線の先をoverride func viewDidLoad()の上に持ってきてドロップする. するとダイアログが出てきて,Nameのところにラベルやボタンの名前を入力する(これはプログラム内で使われる名前). これを残りのラベルとボタンでも同じことをするとこんな感じになる.宣言を勝手にやってくれる.まじかよ.

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var Get: UIButton!
    @IBOutlet weak var Light: UILabel!
    @IBOutlet weak var Acceleration: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

このコード内のoverride func viewDidLoad()は画面をロードするときに呼び出される処理なので, その中にLabelのテキストを記述して,開発ウィンドウ左上の図赤枠部をクリックして実行 f:id:appli-in:20180608204436p:plain:w600

    //毎回画面をロードするときに呼ばれる関数
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        Light.text = "Light : " //Label に "Light :" が表示される
        Acceleration.text = "Acceleration : "//Label に "Acceleration : " が表示される
    }

するとシュミレータが起動してiPhoneの画面が出てくる.シュミレータ上の"Sensor"アプリをタップ.

f:id:appli-in:20180608204653p:plain:w200f:id:appli-in:20180608204822p:plain:w200
シュミレータ起動画面と起動後の画面

表示された.

すごい......

ここまでのあまりの簡単さに感動.

センサー値を取得してみる

輝度センサーの取得
let brightness = UIScreen.mainScreen().brightness

0.0 ~ 1.0 の値が返ります.簡単だ...

これは,画面の明るさの数値で,画面の明るさの設定を変えると変化します.

加速度センサーの取得

こちらはちょっと大変.まずは ”CoreMotion.framework” を追加する. やり方は,画面左部のプロジェクト(今回はSensor),下の図の赤枠部分を選択.

f:id:appli-in:20180609031851p:plain

すると,中心に下図のような画面がでるので,赤枠の "Build Phasese" をクリックする.

"Link Binary With Libraries" の中で "CoreMotion.framework" を追加する.(+マークを押せばダイアログが開いて追加できる) f:id:appli-in:20180609032244p:plain:w600

これでCoreMotionを使えるので,CoreMotionをインポートできる.

import CoreMotion //CoreMotionをインポート

インスタンス生成の生成は

let manager = CMMotionManager() //CoreMotionManagerのインスタンス生成

アップデートするまでの処理は以下の通り.

        manager.accelerometerUpdateInterval = 1 / 2; // アップデート間隔:2Hz
        
        let accelerometerHandler: CMAccelerometerHandler = {
            [weak self] data, error in
            
            self?.Acceleration.text = "Accel(x) : \(data!.acceleration.x)" //Label に "Acceleration : x軸" が表示される xをyやzに変えれば同様に取得できる.
        }
        
        manager.startAccelerometerUpdates (to: OperationQueue.current!, withHandler:accelerometerHandler)

最後に加速度センサーを止めるために,viewDidDisappear関数内などで取得をストップする.今回はボタンを押したときのみ取得なので,アップロード後にすぐに止めています.

        if manager.isAccelerometerAvailable {
            manager.stopAccelerometerUpdates()
        }
ボタンの設定

ボタンの設定も簡単でラベルのときと同じように,ボタンをcontrolキーを押しながらエディタの上に持っていくと青い線が出てきて,適切な場所でドロップすると,下のような画面が出てきて,ConectionをActionにするとドラッグした場所に関数が作成されます.その関数はそのボタンを押したときに実行される関数です. f:id:appli-in:20180611011245p:plain

コード

今回の実装のために書いたコードは以下の通り.今回は加速度はx軸のみ表示させています.ボタンを押したときの加速度を表示させたかったので,アップデート間隔を2Hzにして,ボタンを押したときのみ実行される関数内でアップデートの処理を止めています.(あんまりスマートな書き方ではないと思うので,もし知っている方が入れば教えてください)

import UIKit
import CoreMotion //CoreMotionをインポート

class ViewController: UIViewController {

    @IBOutlet weak var Get: UIButton!
    @IBOutlet weak var Light: UILabel!
    @IBOutlet weak var Acceleration: UILabel!
    
    let manager = CMMotionManager() //CoreMotionManagerのインスタンス生成
    
    //毎回画面をロードするときに呼ばれる関数
    override func viewDidLoad() {
        super.viewDidLoad()
        Light.text = "Light : "
        Acceleration.text = "Accel : "
    }
    
    //ボタンを押したときに実行される関数
    @IBAction func getSensor(_ sender: Any) {
        
        //光センサー値取得
        let brightness = UIScreen.main.brightness //value : 0.0 ~ 1.0
        Light.text = "Light : \(brightness)"  //Label に "Light : 数値"
        
        manager.accelerometerUpdateInterval = 1 / 2; // アップデート間隔:2Hz
        
        let accelerometerHandler: CMAccelerometerHandler = {
            [weak self] data, error in
            
            self?.Acceleration.text = "Accel(x) : \(data!.acceleration.x)" //Label に "Acceleration : x軸" が表示される
        }
        
        //アップデートスタート
        manager.startAccelerometerUpdates (to: OperationQueue.current!, withHandler:accelerometerHandler)

        sleep(1)
        
        if manager.isAccelerometerAvailable {
            //アップデートストップ
            manager.stopAccelerometerUpdates()
        }
    }
    
    //画面を消したときに実行される関数
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

実機でテスト

プログラムがかけたので実機でテストします.

この部分はかなりエラーが出やすいので予め設定をします.

XcodeのPeferencesから"Accounts"タブを開いて,自分のAppleIDを登録してください.

次に,Project Navigatorからプロジェクト(下図左上の赤枠)を選択して,Build Settingsを選択.Product Bundle Identifier jp.他と被らない文字列(自分のメールアドレスなど).プロジェクト名にするとエラーが起きにくいです.SampleとかTestとかだと被りやすいので注意.

”Signing”の設定で”Code Signing Identify”を"iOS Developer"に”Developer Team”を先程登録したAppleIDのアカウントにしてください.

f:id:appli-in:20180611014210p:plain:w600

次に,iPhoneをUSBでPCに接続します.このときに,PCを信頼するか聞かれるので信頼してください.そうしたら下図のように画面上部に端末名が表示されることを確認します(図では黒線が引かれています).

f:id:appli-in:20180611013331p:plain

そして実行(再生ボタンみたいなの)を実行.

するとビルドされて端末にインストールされます.端末にインストールされたアプリを開こうとすると,"信頼されていないエンタープライズ開発元"という表示がでてアプリを実行できません.iPhoneの設定から設定→一般→プロファイルとデバイス管理で自分の開発用アカウントを選択アプリを承認するとアプリを実行可能になります.

実際にアプリを起動して実行したらこんな感じ

f:id:appli-in:20180611015920p:plain:w300

まとめ

iOSアプリ作るのってもっと大変だと思ってたけど,想像していたよりも簡単だった.ビジュアルプログラミングみたいな感じで,レイアウトとかボタンの設定とかが直感的に操作できるのは本当に感動した.簡単なアプリだったら手軽に作れるんだろうなと思った.

これだけは簡単じゃなかったんだけど,実機テストの部分で結構エラーが出て大変だったのでもしエラー出て困ってる人がいたらコメントしてくれたら対応できるかもです.(後日対応記事を書きたいと思っている).

大学院卒から見た院進と就職

大学院に進学するか就職するかは結構迷うと思います。

先日この記事を読んで paiza.hatenablog.com

実際に大学院に進学した者として感じたこと・思ったことまとめた.

学部卒と院卒の違い

大きく2つの違いがあると思う.
1つ目は,就職率や就職先の違い.2つ目は身につく能力の違い.

就職率や就職先

特に研究職は修士卒が条件になっていることが多いです.そもそも研究したかったら大学院にいっているという話ではある.周りも平均して大手企業に就職しているのは大学院卒のイメージが強い.このご時世大手企業に行ったからといって正解ってわけでもないのであんまり意識しなくてもいいかなと思います.大学院に進学するとほぼ必然的に研究を頑張るはずだし,学会発表もする人がほとんどなので,就職活動では話すネタには困りません.業績がそこそこあると印象も良い.ただ,他の院生ももれなく頑張ってきている方々なのでどうやってそこで差別化するかみたいなことは考える必要がある.IT分野は割と売り手市場なので,受けた企業は2社だけだった.周りもそんなに多くはなく5社受けていれば多いほうでした.そもそもあんまりむやみに受けるものでもないと思います.

身につく能力

研究室によって程度の差はあるかもしれないが,学部生と修士生では求められるものが違います.少なくとも自分のいた研究室では,指導教員から以下のようなことが求められていました.

  • 学部生は問題が与えられていて、それを指導教員と共に解決していくこと
  • 修士過程では問題が存在し、その問題への解決策は自分で考えて問題を解決すること
  • (ちなみに博士過程では,問題を自分で見つけ出し、それに対して解決策を提示すること)

ちょっとニュアンスに誤差はあるけど大体こんな感じでした.

働いてみて指導教員の言っていたことはこういう事かと実感できる機会が多々ありました.また,修士の学生は学部生に対して平均して考えていた時間が多いです.答えのない課題に対してどれだけ考え抜くことができるか.この能力が修士卒は学部卒に比べて高いです.実際,採用においても修士卒は開発スキルや研究内容を活かせるかみたいなことよりも,この能力を求められている印象を受けました.

院卒のメリット・デメリット

メリット

正直あと2年学生やれるってのは大きなメリットかなと感じます.特に就職するってなったときに先輩社員の話とかよりも,学部で就職した友達とかの話とかの方がリアルで参考になったし,自分は研究室に入ってからやりたいことや好きなことが増えたので,それらをやったり勉強したりする時間が得られた価値はかなり大きいです.実際就職して一日9時間(通勤時間も含めると12時間!!)も拘束されているので,それに比べるとと思いますね.

研究を頑張れば学会発表で研究室のお金で海外に行けるというのも大きなメリットです.自分が普段行かないような土地で学会が開催されることもあるので行けたときはいい経験になると思います.ここで開催される学会に参加したいから研究を頑張るというモチベーションにもなります.

その他のメリットとして,社会人の方が楽しいと感じられることですかね.研究は誰かに求められているわけではないし,給料も出ないのに時間だけは取られます.就職すれば仕事として需要があって,更に給料もでるので楽しいと感じやすいです.

デメリット

社会人になるのが2年遅れることが大きなデメリットに感じました.今後の生活基盤を作り始めるタイミングが少し遅れます.ほとんどの大学院生はお金がないので貯金もしづらいです.

あと,大学院生活はほとんどが研究室での生活になるので,外部との関わりが少なくなりがちです.なので,できれば積極的に外部のコミュニティに参加するようにした方がいいかもしれません.

お金の話

デメリットでも書いたけれどお金がない.バイトも研究室によっては厳しいし,できたとしても十分に稼ぐことは厳しいと思います.それよりも研究することをおすすめします.自分もお金がなかったので日本学生支援機構の奨学金を借りていました.第一種(利子なし)と第二種(利子あり)があります.自分は第一種で借りていて,一人暮らしで月に8万8000円.2年間で211万2000円.結構大きいですよね.日本学生支援機構の大学院の奨学金は親の年収は関係ありません.第一種に関しては業績次第では全額or半額返済が免除になるので,積極的に申し込んだ方がいいです.

そんなことよりも大事なこと

大学院進学で大事なのは研究室と指導教員です.正直これに失敗すると,2年間とお金を無駄にすると思います.研究が好きで大学院行きたいけど,今の研究室は...とか指導教員と合わない...とかがあるんだったら,他の大学の大学院を検討したほうがいいです.本当に!!.幸いにも自分は研究室にも指導教員にも恵まれましたが,そうでなかったらと思うとゾッとします.  

まとめ

大学院卒の立場から就職か進学かについてまとめました.

いろいろと書いてきましたが結局,どちらを選んでも正解だと思います.どちらが正解かというよりも自分の選択を正解にすることの方が大事かなと感じます.大学院に進学したい人で「大学院に進学してこの研究をちゃんとやりたい」と思って進学できるのであれば十分かなと思います.