teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

2

Unity 2019で見たテクスチャプレビューを追記

2020/12/01 10:32

投稿

Bongo
Bongo

スコア10816

answer CHANGED
@@ -280,4 +280,8 @@
280
280
 
281
281
  せっかくなのでオブジェクトに適用してみました。なおテクスチャ自体はグレーですが、下図では階調に応じて着色し見やすくしてあります。Z方向にも次元を持つテクスチャですので、任意の向きで断面を描かせることができました。
282
282
 
283
- ![図4](b67ca52ac550624b9706c1b0100da007.gif)
283
+ ![図4](b67ca52ac550624b9706c1b0100da007.gif)
284
+
285
+ ##Unity 2019.4.13f1で見たHeadの様子
286
+
287
+ ![図5](752198f42775d10dae379dc77db40a49.gif)

1

ネームスペースを削除、エディター用スクリプトであることを勘案し文面を修正

2020/12/01 10:32

投稿

Bongo
Bongo

スコア10816

answer CHANGED
@@ -8,7 +8,7 @@
8
8
  > var texture = image.RenderImage().AsTexture2D();
9
9
  > ```
10
10
 
11
- だけで`Texture2D`ができあがるという親切お手軽設計でした。そこで凹みTipsさんとはちょっと違ったアプローチですが、まずfo-dicomをインポートした上で下記のようなスクリプトを用意し...
11
+ だけで`Texture2D`ができあがるという親切お手軽設計でした。そこで凹みTipsさんとはちょっと違ったアプローチですが、まずfo-dicomをインポートした上で、プロジェクト内にEditorフォルダを作って下記のようなスクリプトを置き...
12
12
 
13
13
  ```C#
14
14
  using System;
@@ -19,250 +19,247 @@
19
19
  using UnityEngine;
20
20
  using Object = UnityEngine.Object;
21
21
 
22
- namespace DicomToTexture3D.Editor
22
+ public static class DicomFilesToTexture3D
23
23
  {
24
+ [MenuItem("Utility/DICOM/Create Texture3D from DICOM files")]
24
- public static class DicomFilesToTexture3D
25
+ private static void CreateTexture3DFromDicomFiles()
25
26
  {
26
- [MenuItem("Utility/DICOM/Create Texture3D from DICOM files")]
27
+ // まずフォルダ選択ダイアログを出し、DICOMファイル群が収められたフォルダを選んでもらう
27
- private static void CreateTexture3DFromDicomFiles()
28
+ var sourceFolderPath = EditorUtility.OpenFolderPanel(
29
+ "Choose a folder that contains DICOM (*.dcm, *.dic) images",
30
+ string.Empty,
31
+ string.Empty);
32
+ if (string.IsNullOrEmpty(sourceFolderPath))
28
33
  {
29
- // まずフォルダ選択ダイアログを出し、DICOMファイル群が収められたフォルダを選んでもらう
30
- var sourceFolderPath = EditorUtility.OpenFolderPanel(
31
- "Choose a folder that contains DICOM (*.dcm, *.dic) images",
32
- string.Empty,
33
- string.Empty);
34
- if (string.IsNullOrEmpty(sourceFolderPath))
35
- {
36
- return;
34
+ return;
37
- }
35
+ }
38
36
 
39
- if (!Directory.Exists(sourceFolderPath))
37
+ if (!Directory.Exists(sourceFolderPath))
40
- {
38
+ {
41
- EditorUtility.DisplayDialog("Invalid source", "Choose a valid source folder.", "OK");
39
+ EditorUtility.DisplayDialog("Invalid source", "Choose a valid source folder.", "OK");
42
- return;
40
+ return;
43
- }
41
+ }
44
42
 
45
- // フォルダ内でファイル名が「.dcm」または「.dic」で終わるものを抜き出してファイルパスを得る
43
+ // フォルダ内でファイル名が「.dcm」または「.dic」で終わるものを抜き出してファイルパスを得る
46
- var sourceFilePaths = Directory.EnumerateFiles(sourceFolderPath).Where(
44
+ var sourceFilePaths = Directory.EnumerateFiles(sourceFolderPath).Where(
47
- path => !string.IsNullOrEmpty(path) &&
45
+ path => !string.IsNullOrEmpty(path) &&
48
- (path.EndsWith(".dcm", StringComparison.OrdinalIgnoreCase) ||
46
+ (path.EndsWith(".dcm", StringComparison.OrdinalIgnoreCase) ||
49
- path.EndsWith(".dic", StringComparison.OrdinalIgnoreCase)))
47
+ path.EndsWith(".dic", StringComparison.OrdinalIgnoreCase)))
50
- .OrderBy(path => path).ToArray();
48
+ .OrderBy(path => path).ToArray();
51
- var sourceFileCount = sourceFilePaths.Length;
49
+ var sourceFileCount = sourceFilePaths.Length;
52
- if (sourceFileCount <= 0)
50
+ if (sourceFileCount <= 0)
53
- {
51
+ {
54
- EditorUtility.DisplayDialog("Invalid source", "No DICOM images were found.", "OK");
52
+ EditorUtility.DisplayDialog("Invalid source", "No DICOM images were found.", "OK");
55
- return;
53
+ return;
56
- }
54
+ }
57
55
 
58
- // ひとまず最初のファイルからDicomImageを作り、できあがるであろうTexture3Dの
56
+ // ひとまず最初のファイルからDicomImageを作り、できあがるであろうTexture3Dの
59
- // サイズ見積もりを提示し、続行するかどうか確認する
57
+ // サイズ見積もりを提示し、続行するかどうか確認する
60
- var firstFilePath = sourceFilePaths.First();
58
+ var firstFilePath = sourceFilePaths.First();
61
- DicomImage firstImage;
59
+ DicomImage firstImage;
62
- try
60
+ try
63
- {
61
+ {
64
- firstImage = new DicomImage(firstFilePath);
62
+ firstImage = new DicomImage(firstFilePath);
65
- }
63
+ }
66
- catch (Exception e)
64
+ catch (Exception e)
67
- {
65
+ {
68
- EditorUtility.DisplayDialog("Failed", $"{e.GetType()}\n{e.Message}", "OK");
66
+ EditorUtility.DisplayDialog("Failed", $"{e.GetType()}\n{e.Message}", "OK");
69
- throw;
67
+ throw;
70
- }
68
+ }
71
69
 
72
- var dimension = new Vector3Int(
73
- firstImage.Width,
74
- firstImage.Height,
75
- firstImage.NumberOfFrames > 1 ? firstImage.NumberOfFrames : sourceFileCount);
76
- var resultSize = dimension.x * dimension.y * dimension.z * 4;
77
- var resultSizeInKib = resultSize / 1024.0;
78
- var resultSizeInMib = resultSizeInKib / 1024.0;
79
- var resultSizeInGib = resultSizeInMib / 1024.0;
80
- if (!EditorUtility.DisplayDialog(
81
- "Confirm Texture3D data size",
82
- $"The resulting Texture3D data size will be {resultSize} bytes ({(resultSizeInGib >= 1.0 ? $"{resultSizeInGib:0.##} GB" : resultSizeInMib >= 1.0 ? $"{resultSizeInMib:0.##} MB" : $"{resultSizeInKib:0.##} KB")}). Are you sure want to continue?",
70
+ var dimension = new Vector3Int(
71
+ firstImage.Width,
72
+ firstImage.Height,
73
+ firstImage.NumberOfFrames > 1 ? firstImage.NumberOfFrames : sourceFileCount);
74
+ var resultSize = dimension.x * dimension.y * dimension.z * 4;
75
+ var resultSizeInKib = resultSize / 1024.0;
76
+ var resultSizeInMib = resultSizeInKib / 1024.0;
77
+ var resultSizeInGib = resultSizeInMib / 1024.0;
78
+ if (!EditorUtility.DisplayDialog(
79
+ "Confirm Texture3D data size",
80
+ $"The resulting Texture3D data size will be {resultSize} bytes ({(resultSizeInGib >= 1.0 ? $"{resultSizeInGib:0.##} GB" : resultSizeInMib >= 1.0 ? $"{resultSizeInMib:0.##} MB" : $"{resultSizeInKib:0.##} KB")}). Are you sure want to continue?",
81
+ "Continue",
82
+ "Cancel"))
83
+ {
84
+ return;
85
+ }
86
+
87
+ Texture3D resultTexture;
88
+ if (firstImage.NumberOfFrames > 1)
89
+ {
90
+ // DICOMは1つのファイルに複数枚のフレームを持つこともできるらしい?
91
+ // それらフレームが個々の断面を表しているケースもあるのかもしれない...と思い
92
+ // 先ほど作ったDicomImageに複数のフレームが格納されている場合は、それぞれの
93
+ // フレームを断面と見なすことにし、もしフォルダ内に他のDICOMファイルが入っていたとしても
94
+ // それらは無視することにした
95
+ if ((sourceFileCount > 1) && !EditorUtility.DisplayDialog(
96
+ "Texture3D will be created from single file",
97
+ $"{sourceFileCount} DICOM images exist in the folder {Path.GetFileName(sourceFolderPath)}, but file {Path.GetFileName(firstFilePath)} has multiple frames and the Texture3D slices will be taken from this file. All other files will be ignored.",
83
98
  "Continue",
84
99
  "Cancel"))
85
100
  {
86
101
  return;
87
102
  }
88
103
 
89
- Texture3D resultTexture;
104
+ // まずTexture3Dを生成して...
105
+ resultTexture = new Texture3D(dimension.x, dimension.y, dimension.z, TextureFormat.RGBA32, false);
90
- if (firstImage.NumberOfFrames > 1)
106
+ for (var i = 0; i < firstImage.NumberOfFrames; i++)
91
107
  {
92
- // DICOMは1つのァイルに複数枚のフレームを持つこともできるらしい?
93
- // それらフレームが個々断面表しているケスもあるかもしれない...と思い
94
- // 先ほど作ったDicomImageに複数のフレームが格納されている場合は、それぞれの
95
- // フレームを断面と見なすことにし、もしフォルダ内に他のDICOMファイルが入っていたとしても
96
- // それらは無視することにした
97
- if ((sourceFileCount > 1) && !EditorUtility.DisplayDialog(
98
- "Texture3D will be created from single file",
99
- $"{sourceFileCount} DICOM images exist in the folder {Path.GetFileName(sourceFolderPath)}, but file {Path.GetFileName(firstFilePath)} has multiple frames and the Texture3D slices will be taken from this file. All other files will be ignored.",
100
- "Continue",
101
- "Cancel"))
108
+ // フレームからUnityImage生成、さにそれからTexture2Dを生成
109
+ // そのデータTexture3Dにコピ、最後に用済みTexture2Dを削除する
110
+ try
102
111
  {
103
- return;
104
- }
105
-
106
- // まずTexture3Dを生成して...
107
- resultTexture = new Texture3D(dimension.x, dimension.y, dimension.z, TextureFormat.RGBA32, false);
108
- for (var i = 0; i < firstImage.NumberOfFrames; i++)
112
+ using (var tempImage = firstImage.RenderImage(i))
109
- {
110
- // 各フレームからUnityImageを生成、さらにそれからTexture2Dを生成して
111
- // そのデータをTexture3Dにコピー、最後に用済みのTexture2Dを削除する
112
- try
113
113
  {
114
- using (var tempImage = firstImage.RenderImage(i))
115
- {
116
- var tempTexture = tempImage.AsTexture2D();
114
+ var tempTexture = tempImage.AsTexture2D();
117
- Graphics.CopyTexture(tempTexture, 0, 0, resultTexture, i, 0);
115
+ Graphics.CopyTexture(tempTexture, 0, 0, resultTexture, i, 0);
118
- Object.DestroyImmediate(tempTexture);
116
+ Object.DestroyImmediate(tempTexture);
119
- }
120
117
  }
121
- catch (Exception e)
122
- {
123
- EditorUtility.DisplayDialog("Failed", $"{e.GetType()}\n{e.Message}", "OK");
124
- Object.DestroyImmediate(resultTexture);
125
- throw;
126
- }
127
118
  }
119
+ catch (Exception e)
120
+ {
121
+ EditorUtility.DisplayDialog("Failed", $"{e.GetType()}\n{e.Message}", "OK");
122
+ Object.DestroyImmediate(resultTexture);
123
+ throw;
124
+ }
128
125
  }
126
+ }
129
- else
127
+ else
128
+ {
129
+ // 最初に調べた1枚目のDicomImageがシングルフレームだったなら、
130
+ // 断面がそれぞれ1ファイルずつに分けられているものと想定する
131
+ // まずTexture3Dを生成して...
132
+ resultTexture = new Texture3D(dimension.x, dimension.y, dimension.z, TextureFormat.RGBA32, false);
133
+ for (var i = 0; i < sourceFilePaths.Length; i++)
130
134
  {
131
- // 最初に調べた1枚目のDicomImageがシングルフレームだったなら、
132
- // 断面がそれぞれ1ファイルずつに分けられていものと想定する
135
+ // フォルダ内のファイルを1つずつ列挙し、DicomImageを作
133
- // まずTexture3Dを生成して...
134
- resultTexture = new Texture3D(dimension.x, dimension.y, dimension.z, TextureFormat.RGBA32, false);
135
- for (var i = 0; i < sourceFilePaths.Length; i++)
136
+ var sourceFilePath = sourceFilePaths[i];
137
+ var sourceFileName = Path.GetFileName(sourceFilePath);
138
+ DicomImage image;
139
+ try
136
140
  {
137
- // フォルダ内のファイルを1つずつ列挙し、DicomImageを作る
138
- var sourceFilePath = sourceFilePaths[i];
141
+ image = new DicomImage(sourceFilePath);
139
- var sourceFileName = Path.GetFileName(sourceFilePath);
142
+ }
140
- DicomImage image;
143
+ catch (Exception e)
141
- try
142
- {
144
+ {
145
+ EditorUtility.DisplayDialog("Failed", $"{e.GetType()}\n{e.Message}", "OK");
143
- image = new DicomImage(sourceFilePath);
146
+ Object.DestroyImmediate(resultTexture);
147
+ throw;
144
- }
148
+ }
145
- catch (Exception e)
146
- {
147
- EditorUtility.DisplayDialog("Failed", $"{e.GetType()}\n{e.Message}", "OK");
148
- Object.DestroyImmediate(resultTexture);
149
- throw;
150
- }
151
149
 
152
- // こちらのケースでは1つのファイルが1つの断面を表していることを前提としており、
150
+ // こちらのケースでは1つのファイルが1つの断面を表していることを前提としており、
153
- // もしそこに複数フレームを持つファイルがまぎれ込んでいたら異常事態と見なして中断する
151
+ // もしそこに複数フレームを持つファイルがまぎれ込んでいたら異常事態と見なして中断する
154
- if (image.NumberOfFrames > 1)
152
+ if (image.NumberOfFrames > 1)
155
- {
153
+ {
156
- EditorUtility.DisplayDialog(
154
+ EditorUtility.DisplayDialog(
157
- "Invalid source",
155
+ "Invalid source",
158
- $"A multi-frame image {sourceFileName} was found.",
156
+ $"A multi-frame image {sourceFileName} was found.",
159
- "OK");
157
+ "OK");
160
- Object.DestroyImmediate(resultTexture);
158
+ Object.DestroyImmediate(resultTexture);
161
- return;
159
+ return;
162
- }
160
+ }
163
161
 
164
- // また、すべての断面は幅・高さが同じでないとまずいため、
162
+ // また、すべての断面は幅・高さが同じでないとまずいため、
165
- // もしそうでないファイルがまぎれ込んでいた場合も中断する
163
+ // もしそうでないファイルがまぎれ込んでいた場合も中断する
166
- if ((image.Width != dimension.x) || (image.Height != dimension.y))
164
+ if ((image.Width != dimension.x) || (image.Height != dimension.y))
167
- {
165
+ {
168
- EditorUtility.DisplayDialog(
166
+ EditorUtility.DisplayDialog(
169
- "Invalid source",
167
+ "Invalid source",
170
- $"All images should be the same size. The expected size is ({dimension.x}, {dimension.y}), but the size of image {sourceFileName} is ({image.Width}, {image.Height})",
168
+ $"All images should be the same size. The expected size is ({dimension.x}, {dimension.y}), but the size of image {sourceFileName} is ({image.Width}, {image.Height})",
171
- "OK");
169
+ "OK");
172
- Object.DestroyImmediate(resultTexture);
170
+ Object.DestroyImmediate(resultTexture);
173
- return;
171
+ return;
174
- }
172
+ }
175
173
 
176
- // DicomImageからUnityImageを生成、さらにそれからTexture2Dを生成して
174
+ // DicomImageからUnityImageを生成、さらにそれからTexture2Dを生成して
177
- // そのデータをTexture3Dにコピー、最後に用済みのTexture2Dを削除する
175
+ // そのデータをTexture3Dにコピー、最後に用済みのTexture2Dを削除する
178
- try
176
+ try
177
+ {
178
+ using (var tempImage = image.RenderImage())
179
179
  {
180
- using (var tempImage = image.RenderImage())
181
- {
182
- var tempTexture = tempImage.AsTexture2D();
180
+ var tempTexture = tempImage.AsTexture2D();
183
- Graphics.CopyTexture(tempTexture, 0, 0, resultTexture, i, 0);
181
+ Graphics.CopyTexture(tempTexture, 0, 0, resultTexture, i, 0);
184
- Object.DestroyImmediate(tempTexture);
182
+ Object.DestroyImmediate(tempTexture);
185
- }
186
183
  }
187
- catch (Exception e)
188
- {
189
- EditorUtility.DisplayDialog("Failed", $"{e.GetType()}\n{e.Message}", "OK");
190
- Object.DestroyImmediate(resultTexture);
191
- throw;
192
- }
193
184
  }
185
+ catch (Exception e)
186
+ {
187
+ EditorUtility.DisplayDialog("Failed", $"{e.GetType()}\n{e.Message}", "OK");
188
+ Object.DestroyImmediate(resultTexture);
189
+ throw;
190
+ }
194
191
  }
195
-
196
- // セーブダイアログを出してアセットの名前、保存場所を決めてもらい...
197
- var destinationPath = EditorUtility.SaveFilePanelInProject(
198
- "Save Texture3D",
199
- "DICOM Texture",
200
- "asset",
201
- "Enter an asset name for the Texture3D.");
202
- if (string.IsNullOrEmpty(destinationPath))
203
- {
204
- Object.DestroyImmediate(resultTexture);
205
- return;
206
- }
207
-
208
- // できあがったTexture3Dをアセットファイル化して保存する
209
- AssetDatabase.CreateAsset(resultTexture, destinationPath);
210
- AssetDatabase.SaveAssets();
211
192
  }
212
193
 
213
- // fo-dicomの作るTexture2DがRGBA32フォーマットだったので生成されるTexture3Dのフォーマットも
214
- // RGBA32にたも今回のようなグレースケールに4チャンネル使うのはもったなく思い
194
+ // セーブダイアログを出てアセット名前保存場所を決めて...
215
- // 生成されたRGBA32のTexture3DからRだけを抜き出してR8フォーマット版を作るメソッドも用意した
195
+ var destinationPath = EditorUtility.SaveFilePanelInProject(
196
+ "Save Texture3D",
197
+ "DICOM Texture",
198
+ "asset",
216
- [MenuItem("Utility/DICOM/Convert Texture3D format from RGBA32 to/R8")]
199
+ "Enter an asset name for the Texture3D.");
217
- private static void ConvertToR8()
200
+ if (string.IsNullOrEmpty(destinationPath))
218
201
  {
219
- ConvertTo(DestinationFormat.R8);
202
+ Object.DestroyImmediate(resultTexture);
203
+ return;
220
204
  }
221
205
 
222
- // こちらは抜出しRAとして扱わせ
206
+ // あがっTexture3Dアセットファイル化して保存す
223
- [MenuItem("Utility/DICOM/Convert Texture3D format from RGBA32 to/Alpha8")]
224
- private static void ConvertToAlpha8()
207
+ AssetDatabase.CreateAsset(resultTexture, destinationPath);
225
- {
226
- ConvertTo(DestinationFormat.Alpha8);
208
+ AssetDatabase.SaveAssets();
227
- }
209
+ }
228
210
 
211
+ // fo-dicomの作るTexture2DがRGBA32フォーマットだったので生成されるTexture3Dのフォーマットも
212
+ // RGBA32にしたものの、今回のようなグレースケールに4チャンネルも使うのはもったいなく思い
213
+ // 生成されたRGBA32のTexture3DからRだけを抜き出してR8フォーマット版を作るメソッドも用意した
214
+ [MenuItem("Utility/DICOM/Convert Texture3D format from RGBA32 to/R8")]
229
- private static void ConvertTo(DestinationFormat format)
215
+ private static void ConvertToR8()
230
- {
216
+ {
231
- var targetTexture = Selection.activeObject as Texture3D;
217
+ ConvertTo(DestinationFormat.R8);
232
- if ((targetTexture == null) || (targetTexture.format != TextureFormat.RGBA32))
233
- {
234
- return;
235
- }
218
+ }
236
219
 
220
+ // こちらは抜き出したRをAとして扱わせる
237
- var newData = targetTexture.GetPixels32(0).Select(c => c.r).ToArray();
221
+ [MenuItem("Utility/DICOM/Convert Texture3D format from RGBA32 to/Alpha8")]
238
- var newTexture = new Texture3D(
222
+ private static void ConvertToAlpha8()
239
- targetTexture.width,
240
- targetTexture.height,
241
- targetTexture.depth,
242
- (TextureFormat)format,
243
- false);
244
- newTexture.SetPixelData(newData, 0);
245
- var destinationPath = EditorUtility.SaveFilePanelInProject(
246
- "Save Texture3D",
247
- $"{targetTexture.name} {format}",
248
- "asset",
249
- "Enter an asset name for the Texture3D.");
250
- if (string.IsNullOrEmpty(destinationPath))
251
- {
223
+ {
252
- Object.DestroyImmediate(newTexture);
224
+ ConvertTo(DestinationFormat.Alpha8);
253
- return;
254
- }
225
+ }
255
226
 
256
- AssetDatabase.CreateAsset(newTexture, destinationPath);
227
+ private static void ConvertTo(DestinationFormat format)
228
+ {
257
- AssetDatabase.SaveAssets();
229
+ var targetTexture = Selection.activeObject as Texture3D;
230
+ if ((targetTexture == null) || (targetTexture.format != TextureFormat.RGBA32))
231
+ {
232
+ return;
258
233
  }
259
234
 
235
+ var newData = targetTexture.GetPixels32(0).Select(c => c.r).ToArray();
236
+ var newTexture = new Texture3D(
237
+ targetTexture.width,
238
+ targetTexture.height,
239
+ targetTexture.depth,
240
+ (TextureFormat)format,
241
+ false);
242
+ newTexture.SetPixelData(newData, 0);
243
+ var destinationPath = EditorUtility.SaveFilePanelInProject(
244
+ "Save Texture3D",
245
+ $"{targetTexture.name} {format}",
246
+ "asset",
247
+ "Enter an asset name for the Texture3D.");
260
- private enum DestinationFormat
248
+ if (string.IsNullOrEmpty(destinationPath))
261
249
  {
262
- R8 = TextureFormat.R8,
263
- Alpha8 = TextureFormat.Alpha8
250
+ Object.DestroyImmediate(newTexture);
251
+ return;
264
252
  }
253
+
254
+ AssetDatabase.CreateAsset(newTexture, destinationPath);
255
+ AssetDatabase.SaveAssets();
265
256
  }
257
+
258
+ private enum DestinationFormat
259
+ {
260
+ R8 = TextureFormat.R8,
261
+ Alpha8 = TextureFormat.Alpha8
262
+ }
266
263
  }
267
264
  ```
268
265