diff --git a/Pipfile.lock b/Pipfile.lock index 64e28f87..d7ba537d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -70,9 +70,9 @@ }, "bokeh": { "hashes": [ - "sha256:e2d97bed5b199a10686486001fed5c854e4c04ebe28859923f27c52b93904754" + "sha256:c60d38a41a777b8147ee4134e6142cea8026b5eebf48149e370c44689869dce7" ], - "version": "==1.3.4" + "version": "==1.4.0" }, "certifi": { "hashes": [ @@ -127,10 +127,10 @@ }, "dataclasses-json": { "hashes": [ - "sha256:3f348a132c6c84772b99fca50c447ef3b8382d274fd9a539c958dd9b93ba4806", - "sha256:8851be971187d22a898247ffff9b23de6d5d1db93b8e648997a71a2e4023d13c" + "sha256:616876ac06ca3a8f2ae977a4e044850e81dbd3f86a404430b783e12f017b75b5", + "sha256:ebdf7407681763d6125fd00d15ed037cc3aa6f9129fe7634e8f891410e89559f" ], - "version": "==0.3.5" + "version": "==0.3.6" }, "dictdiffer": { "hashes": [ @@ -306,10 +306,10 @@ }, "marshmallow": { "hashes": [ - "sha256:077b4612f5d3b9333b736fdc6b963d2b46d409070f44ff3e6c4109645c673e83", - "sha256:9a2f3e8ea5f530a9664e882d7d04b58650f46190178b2264c72b7d20399d28f0" + "sha256:1a358beb89c2b4d5555272065a9533591a3eb02f1b854f3c4002d88d8f2a1ddb", + "sha256:eb97c42c5928b5720812c9268865fe863d4807bc1a8b48ddd7d5c9e1779a6af0" ], - "version": "==3.2.1" + "version": "==3.2.2" }, "marshmallow-enum": { "hashes": [ @@ -394,27 +394,27 @@ }, "pandas": { "hashes": [ - "sha256:0f484f43658a72e7d586a74978259657839b5bd31b903e963bb1b1491ab51775", - "sha256:0ffc6f9e20e77f3a7dc8baaad9c7fd25b858b084d3a2d8ce877bc3ea804e0636", - "sha256:23e0eac646419c3057f15eb96ab821964848607bf1d4ea5a82f26565986ec5e9", - "sha256:27c0603b15b5c6fa24885253bbe49a0c289381e7759385c59308ba4f0b166cf1", - "sha256:397fe360643fffc5b26b41efdf608647e3334a618d185a07976cd2dc51c90bce", - "sha256:3dbb3aa41c01504255bff2bd56944bdb915f6c9ce4bac7e2868efbace0b2a639", - "sha256:4e07c63247c59d61c6ebdbbb50196143cec6c5044403510c4e1a9d31854a83d6", - "sha256:4fa6d9235c6d2fecbeca82c3d326abd255866cafbfd37f66a0e826544e619760", - "sha256:56cb88b3876363d410a9d7724f43641ff164e2c9902d3266a648213e2efd5e6d", - "sha256:7ce1be1614455f83710b9a5dc1fc602a755bdddbe4dda1d41515062923a37bbf", - "sha256:ae1c96ffdeec376895e533107e0b0f9da16225a2184fbb24a5abc866769db75e", - "sha256:b6f27c9231be8a23de846f2302373991467dd8e397a4804d2614e8c5aa8d5a90", - "sha256:c6056067f894f9355bedcd168dd740aa849908d41c0a74756f6e38f203e941b3", - "sha256:ca91a19d1f0a280874a24dca44aadce42da7f3a7edb7e9ab7c7baad8febee2be", - "sha256:cbe4985f5c82a173f8cff6b7fe92d551addf99fb4ea9ff4eb4b1fe606bb098ec", - "sha256:e3e9893bfe80a8b8e6d56d36ebb7afe1df77db7b4068a6e2ef3636a91f6f1caa", - "sha256:e7b218e8711910dac3fed0d19376cd1ef0e386be5175965d332fd0c65d02a43b", - "sha256:ec48d18b8b63a5dbb838e8ea7892ee1034299e03f852bd9b6dffe870310414dd", - "sha256:f4ab6280277e3208a59bfa9f2e51240304d09e69ffb65abfb4a21d678b495f74" - ], - "version": "==0.25.2" + "sha256:00dff3a8e337f5ed7ad295d98a31821d3d0fe7792da82d78d7fd79b89c03ea9d", + "sha256:22361b1597c8c2ffd697aa9bf85423afa9e1fcfa6b1ea821054a244d5f24d75e", + "sha256:255920e63850dc512ce356233081098554d641ba99c3767dde9e9f35630f994b", + "sha256:26382aab9c119735908d94d2c5c08020a4a0a82969b7e5eefb92f902b3b30ad7", + "sha256:33970f4cacdd9a0ddb8f21e151bfb9f178afb7c36eb7c25b9094c02876f385c2", + "sha256:4545467a637e0e1393f7d05d61dace89689ad6d6f66f267f86fff737b702cce9", + "sha256:52da74df8a9c9a103af0a72c9d5fdc8e0183a90884278db7f386b5692a2220a4", + "sha256:61741f5aeb252f39c3031d11405305b6d10ce663c53bc3112705d7ad66c013d0", + "sha256:6a3ac2c87e4e32a969921d1428525f09462770c349147aa8e9ab95f88c71ec71", + "sha256:7458c48e3d15b8aaa7d575be60e1e4dd70348efcd9376656b72fecd55c59a4c3", + "sha256:78bf638993219311377ce9836b3dc05f627a666d0dbc8cec37c0ff3c9ada673b", + "sha256:8153705d6545fd9eb6dd2bc79301bff08825d2e2f716d5dced48daafc2d0b81f", + "sha256:975c461accd14e89d71772e89108a050fa824c0b87a67d34cedf245f6681fc17", + "sha256:9962957a27bfb70ab64103d0a7b42fa59c642fb4ed4cb75d0227b7bb9228535d", + "sha256:adc3d3a3f9e59a38d923e90e20c4922fc62d1e5a03d083440468c6d8f3f1ae0a", + "sha256:bbe3eb765a0b1e578833d243e2814b60c825b7fdbf4cdfe8e8aae8a08ed56ecf", + "sha256:df8864824b1fe488cf778c3650ee59c3a0d8f42e53707de167ba6b4f7d35f133", + "sha256:e45055c30a608076e31a9fcd780a956ed3b1fa20db61561b8d88b79259f526f7", + "sha256:ee50c2142cdcf41995655d499a157d0a812fce55c97d9aad13bc1eef837ed36c" + ], + "version": "==0.25.3" }, "param": { "hashes": [ @@ -510,10 +510,10 @@ }, "pyparsing": { "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" + "sha256:4acadc9a2b96c19fe00932a38ca63e601180c39a189a696abce1eaab641447e1", + "sha256:61b5ed888beab19ddccab3478910e2076a6b5a0295dffc43021890e136edf764" ], - "version": "==2.4.2" + "version": "==2.4.4" }, "pyquery": { "hashes": [ @@ -540,10 +540,10 @@ }, "python-dateutil": { "hashes": [ - "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", - "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" ], - "version": "==2.8.0" + "version": "==2.8.1" }, "pytz": { "hashes": [ diff --git a/annofabcli/__version__.py b/annofabcli/__version__.py index f43a2786..f54fa443 100644 --- a/annofabcli/__version__.py +++ b/annofabcli/__version__.py @@ -1 +1 @@ -__version__ = '1.13.2' +__version__ = '1.13.3' diff --git a/annofabcli/input_data/put_input_data.py b/annofabcli/input_data/put_input_data.py index 8b0dbbb6..5829024f 100644 --- a/annofabcli/input_data/put_input_data.py +++ b/annofabcli/input_data/put_input_data.py @@ -39,24 +39,25 @@ class PutInputData(AbstractCommandLineInterface): """ def put_input_data(self, project_id: str, csv_input_data: CsvInputData, last_updated_datetime: Optional[str] = None): + + request_body: Dict[str, Any] = {'last_updated_datetime': last_updated_datetime} + if is_file_scheme(csv_input_data.input_data_path): - request_body = { + request_body.update({ 'input_data_name': csv_input_data.input_data_name, 'sign_required': csv_input_data.sign_required, - } + }) file_path = get_file_scheme_path(csv_input_data.input_data_path) logger.debug(f"'{file_path}'を入力データとして登録します。") self.service.wrapper.put_input_data_from_file(project_id, input_data_id=csv_input_data.input_data_id, file_path=file_path, request_body=request_body) else: - request_body = { + request_body.update({ 'input_data_name': csv_input_data.input_data_name, 'input_data_path': csv_input_data.input_data_path, 'sign_required': csv_input_data.sign_required, - } - if last_updated_datetime is not None: - request_body.update({'last_updated_datetime': last_updated_datetime}) + }) self.service.api.put_input_data(project_id, csv_input_data.input_data_id, request_body=request_body) @@ -115,7 +116,7 @@ def put_input_data_list(self, project_id: str, input_data_list: List[CsvInputDat # 入力データを登録 try: - self.put_input_data(project_id, csv_input_data, last_updated_datetime) + self.put_input_data(project_id, csv_input_data, last_updated_datetime=last_updated_datetime) logger.debug(f"入力データを登録しました。" f"input_data_id={csv_input_data.input_data_id}, " f"input_data_name={csv_input_data.input_data_name}") diff --git a/annofabcli/statistics/graph.py b/annofabcli/statistics/graph.py index 7372dfcd..05092d89 100644 --- a/annofabcli/statistics/graph.py +++ b/annofabcli/statistics/graph.py @@ -1,5 +1,5 @@ import logging -from typing import List, Optional +from typing import Any, Dict, List, Optional # pylint: disable=unused-import import bokeh import bokeh.layouts @@ -37,7 +37,7 @@ def __init__(self, table: Table, outdir: str): self.short_project_id = table.project_id[0:8] @staticmethod - def _create_hover_tool(tool_tip_items: List[str] = None): + def _create_hover_tool(tool_tip_items: List[str] = None) -> HoverTool: """ HoverTool用のオブジェクトを生成する。 Returns: @@ -70,12 +70,12 @@ def _plot_line_and_circle(fig, x, y, source, username, color): """ - fig.line(x=x, y=y, source=source, legend=username, line_color=color, line_width=1, muted_alpha=0.2, + fig.line(x=x, y=y, source=source, legend_label=username, line_color=color, line_width=1, muted_alpha=0.2, muted_color=color) - fig.circle(x=x, y=y, source=source, legend=username, muted_alpha=0.0, muted_color=color, color=color) + fig.circle(x=x, y=y, source=source, legend_label=username, muted_alpha=0.0, muted_color=color, color=color) @staticmethod - def _set_legend(fig: bokeh.plotting.Figure, hover_tool): + def _set_legend(fig: bokeh.plotting.Figure, hover_tool: HoverTool): """ 凡例の設定。 Args: @@ -170,6 +170,56 @@ def write_アノテーションあたり作業時間(self, task_df: pd.DataFrame Returns: """ + def write_cumulative_graph(fig_info_list: List[Dict[str, str]], html_title: str): + """ + 累計グラフを出力する。 + + Args: + fig_info_list: + html_title: + + Returns: + + """ + figs: List[bokeh.plotting.Figure] = [] + for fig_info in fig_info_list: + figs.append( + figure(plot_width=1200, plot_height=600, title=fig_info["title"], + x_axis_label=fig_info["x_axis_label"], y_axis_label=fig_info["y_axis_label"])) + + for user_index, user_id in enumerate(first_annotation_user_id_list): # type: ignore + filtered_df = df[df["first_annotation_user_id"] == user_id] + if filtered_df.empty: + logger.debug(f"dataframe is empty. user_id = {user_id}") + continue + + # 列が多すぎるとbokehのグラフが表示されないので絞り込む + columns = tooltip_item + [ + "cumulative_annotation_count", + "cumulative_input_data_count", + "cumulative_task_count", + "cumulative_annotation_worktime_hour", + "cumulative_inspection_worktime_hour", + "cumulative_acceptance_worktime_hour", + "cumulative_inspection_count", + ] + + filtered_df = filtered_df[columns] + source = ColumnDataSource(data=filtered_df) + color = self.my_palette[user_index] + username = filtered_df.iloc[0]["first_annotation_username"] + + for fig, fig_info in zip(figs, fig_info_list): + self._plot_line_and_circle(fig, x=fig_info["x"], y=fig_info["y"], source=source, username=username, + color=color) + + hover_tool = self._create_hover_tool(tooltip_item) + for fig in figs: + self._set_legend(fig, hover_tool) + + bokeh.plotting.reset_output() + bokeh.plotting.output_file(f"{self.outdir}/{self.short_project_id}_{html_title}.html", title=html_title) + bokeh.plotting.save(bokeh.layouts.column(figs)) tooltip_item = [ "task_id", @@ -182,6 +232,7 @@ def write_アノテーションあたり作業時間(self, task_df: pd.DataFrame "inspection_worktime_hour", "acceptance_worktime_hour", "annotation_count", + "input_data_count", "inspection_count", ] @@ -205,8 +256,8 @@ def write_アノテーションあたり作業時間(self, task_df: pd.DataFrame # 累計値を計算 df = self.table.create_cumulative_df(task_df) - # グラフの情報 - fig_info_list = [ + # 横軸が累計のアノテーション数 + fig_info_list_annotation_count = [ dict(x="cumulative_annotation_count", y="cumulative_annotation_worktime_hour", title="アノテーション数と教師付作業時間の累積グラフ", x_axis_label="アノテーション数", y_axis_label="教師付作業時間[hour]"), dict(x="cumulative_annotation_count", y="cumulative_inspection_worktime_hour", @@ -216,42 +267,29 @@ def write_アノテーションあたり作業時間(self, task_df: pd.DataFrame dict(x="cumulative_annotation_count", y="cumulative_inspection_count", title="アノテーション数と検査コメント数の累積グラフ", x_axis_label="アノテーション数", y_axis_label="検査コメント数"), ] - - figs = [] - for fig_info in fig_info_list: - figs.append( - figure(plot_width=1200, plot_height=600, title=fig_info["title"], x_axis_label=fig_info["x_axis_label"], - y_axis_label=fig_info["y_axis_label"])) - - for user_index, user_id in enumerate(first_annotation_user_id_list): - filtered_df = df[df["first_annotation_user_id"] == user_id] - if filtered_df.empty: - logger.debug(f"dataframe is empty. user_id = {user_id}") - continue - - # 列が多すぎるとbokehのグラフが表示されないので絞り込む - columns = tooltip_item + [ - "cumulative_annotation_count", - "cumulative_annotation_worktime_hour", - "cumulative_inspection_worktime_hour", - "cumulative_acceptance_worktime_hour", - "cumulative_inspection_count", - ] - - filtered_df = filtered_df[columns] - source = ColumnDataSource(data=filtered_df) - color = self.my_palette[user_index] - username = filtered_df.iloc[0]["first_annotation_username"] - - for fig, fig_info in zip(figs, fig_info_list): - self._plot_line_and_circle(fig, x=fig_info["x"], y=fig_info["y"], source=source, username=username, - color=color) - - hover_tool = self._create_hover_tool(tooltip_item) - for fig in figs: - self._set_legend(fig, hover_tool) - - bokeh.plotting.reset_output() - bokeh.plotting.output_file(f"{self.outdir}/{self.short_project_id}_アノテーション単位の累計グラフ.html", - title="アノテーション単位の累計グラフ") - bokeh.plotting.save(bokeh.layouts.column(figs)) + write_cumulative_graph(fig_info_list_annotation_count, html_title="アノテーション単位の累計グラフ") + + # 横軸が累計の入力データ数 + fig_info_list_input_data_count = [ + dict(x="cumulative_input_data_count", y="cumulative_annotation_worktime_hour", title="入力データ数と教師付作業時間の累積グラフ", + x_axis_label="入力データ数", y_axis_label="教師付作業時間[hour]"), + dict(x="cumulative_input_data_count", y="cumulative_inspection_worktime_hour", + title="入力データ数と中間検査作業時間の累積グラフ", x_axis_label="入力データ数", y_axis_label="中間検査作業時間[hour]"), + dict(x="cumulative_input_data_count", y="cumulative_acceptance_worktime_hour", title="入力データ数と受入作業時間の累積グラフ", + x_axis_label="入力データ数", y_axis_label="受入作業時間[hour]"), + dict(x="cumulative_input_data_count", y="cumulative_inspection_count", title="アノテーション数と検査コメント数の累積グラフ", + x_axis_label="入力データ数", y_axis_label="検査コメント数"), + ] + write_cumulative_graph(fig_info_list_input_data_count, html_title="入力データ単位の累計グラフ") + + fig_info_list_task_count = [ + dict(x="cumulative_task_count", y="cumulative_annotation_worktime_hour", title="アノテーション数と教師付作業時間の累積グラフ", + x_axis_label="アノテーション数", y_axis_label="教師付作業時間[hour]"), + dict(x="cumulative_task_count", y="cumulative_inspection_worktime_hour", title="アノテーション数と中間検査作業時間の累積グラフ", + x_axis_label="アノテーション数", y_axis_label="中間検査作業時間[hour]"), + dict(x="cumulative_task_count", y="cumulative_acceptance_worktime_hour", title="アノテーション数と受入作業時間の累積グラフ", + x_axis_label="アノテーション数", y_axis_label="受入作業時間[hour]"), + dict(x="cumulative_task_count", y="cumulative_inspection_count", title="アノテーション数と検査コメント数の累積グラフ", + x_axis_label="アノテーション数", y_axis_label="検査コメント数"), + ] + write_cumulative_graph(fig_info_list_task_count, html_title="タスク単位の累計グラフ") diff --git a/annofabcli/statistics/table.py b/annofabcli/statistics/table.py index f3dc77f8..c887f23e 100644 --- a/annofabcli/statistics/table.py +++ b/annofabcli/statistics/table.py @@ -545,7 +545,8 @@ def create_cumulative_df(task_df: pd.DataFrame) -> pd.DataFrame: # 教師付の開始時刻でソートして、indexを更新する df = task_df.sort_values(["first_annotation_account_id", "first_annotation_started_datetime"]).reset_index(drop=True) - + # タスクの累計数を取得するために設定する + df["task_count"] = 1 # 教師付の作業者でgroupby groupby_obj = df.groupby("first_annotation_account_id") @@ -557,7 +558,11 @@ def create_cumulative_df(task_df: pd.DataFrame) -> pd.DataFrame: # タスク完了数、差し戻し数 df["cumulative_inspection_count"] = groupby_obj["inspection_count"].cumsum() df["cumulative_annotation_count"] = groupby_obj["annotation_count"].cumsum() + df["cumulative_input_data_count"] = groupby_obj["input_data_count"].cumsum() + df["cumulative_task_count"] = groupby_obj["task_count"].cumsum() + # 元に戻す + df = df.drop(["task_count"], axis=1) return df def create_worktime_per_image_df(self, aggregation_by: AggregationBy, phase: TaskPhase) -> pd.DataFrame: diff --git a/setup.py b/setup.py index cf2b273b..75389cc3 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ 'pyquery', 'pandas', 'isodate', - 'bokeh >=1.2.0', + 'bokeh >=1.4.0', 'holoviews'], python_requires='>=3.6', classifiers=[