Mercurial > hg > orthanc-stone
comparison Framework/Radiography/RadiographyMaskLayer.cpp @ 475:3c28542229a3 am-touch-events
added a mask layer in the RadiographyWidget (to be cleaned)
author | am@osimis.io |
---|---|
date | Tue, 12 Feb 2019 12:22:13 +0100 |
parents | |
children | a95090305dd4 |
comparison
equal
deleted
inserted
replaced
467:22b80f5c3a1c | 475:3c28542229a3 |
---|---|
1 /** | |
2 * Stone of Orthanc | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2018 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU Affero General Public License | |
9 * as published by the Free Software Foundation, either version 3 of | |
10 * the License, or (at your option) any later version. | |
11 * | |
12 * This program is distributed in the hope that it will be useful, but | |
13 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 * Affero General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU Affero General Public License | |
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 **/ | |
20 | |
21 | |
22 #include "RadiographyMaskLayer.h" | |
23 #include "RadiographyDicomLayer.h" | |
24 | |
25 #include "RadiographyScene.h" | |
26 #include "Core/Images/Image.h" | |
27 #include "Core/Images/ImageProcessing.h" | |
28 #include <Core/OrthancException.h> | |
29 | |
30 namespace OrthancStone | |
31 { | |
32 | |
33 void ComputeMaskExtent(unsigned int& left, unsigned int& right, unsigned int& top, unsigned int& bottom, const std::vector<MaskPoint>& corners) | |
34 { | |
35 left = std::numeric_limits<unsigned int>::max(); | |
36 right = std::numeric_limits<unsigned int>::min(); | |
37 top = std::numeric_limits<unsigned int>::max(); | |
38 bottom = std::numeric_limits<unsigned int>::min(); | |
39 | |
40 for (size_t i = 0; i < corners.size(); i++) | |
41 { | |
42 const MaskPoint& p = corners[i]; | |
43 left = std::min(p.x, left); | |
44 right = std::max(p.x, right); | |
45 bottom = std::max(p.y, bottom); | |
46 top = std::min(p.y, top); | |
47 } | |
48 } | |
49 | |
50 void RadiographyMaskLayer::SetCorners(const std::vector<MaskPoint>& corners) | |
51 { | |
52 corners_ = corners; | |
53 invalidated_ = true; | |
54 } | |
55 | |
56 void RadiographyMaskLayer::Render(Orthanc::ImageAccessor& buffer, | |
57 const AffineTransform2D& viewTransform, | |
58 ImageInterpolation interpolation) const | |
59 { | |
60 if (dicomLayer_.GetWidth() == 0) // nothing to do if the DICOM layer is not displayed (or not loaded) | |
61 return; | |
62 | |
63 if (invalidated_) | |
64 { | |
65 mask_.reset(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, dicomLayer_.GetWidth(), dicomLayer_.GetHeight(), false)); | |
66 | |
67 DrawMask(); | |
68 | |
69 // for (unsigned int i = 0; i < 100; i++) | |
70 // { | |
71 // for (unsigned int j = 0; j < 50; j++) | |
72 // { | |
73 // if ((i + j) % 2 == 1) | |
74 // { | |
75 // Orthanc::ImageAccessor region; | |
76 // mask_->GetRegion(region, i* 20, j * 20, 20, 20); | |
77 // Orthanc::ImageProcessing::Set(region, 255); | |
78 // } | |
79 // } | |
80 // } | |
81 invalidated_ = false; | |
82 } | |
83 | |
84 {// rendering | |
85 if (buffer.GetFormat() != Orthanc::PixelFormat_Float32) | |
86 { | |
87 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
88 } | |
89 | |
90 unsigned int cropX, cropY, cropWidth, cropHeight; | |
91 dicomLayer_.GetCrop(cropX, cropY, cropWidth, cropHeight); | |
92 | |
93 const AffineTransform2D t = AffineTransform2D::Combine( | |
94 viewTransform, dicomLayer_.GetTransform(), | |
95 AffineTransform2D::CreateOffset(cropX, cropY)); | |
96 | |
97 Orthanc::ImageAccessor cropped; | |
98 mask_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); | |
99 | |
100 Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false); | |
101 | |
102 t.Apply(tmp, cropped, interpolation, true /* clear */); | |
103 | |
104 // Blit | |
105 const unsigned int width = buffer.GetWidth(); | |
106 const unsigned int height = buffer.GetHeight(); | |
107 | |
108 for (unsigned int y = 0; y < height; y++) | |
109 { | |
110 float *q = reinterpret_cast<float*>(buffer.GetRow(y)); | |
111 const uint8_t *p = reinterpret_cast<uint8_t*>(tmp.GetRow(y)); | |
112 | |
113 for (unsigned int x = 0; x < width; x++, p++, q++) | |
114 { | |
115 if (*p == 0) | |
116 *q = foreground_; | |
117 // else keep the underlying pixel value | |
118 } | |
119 } | |
120 | |
121 } | |
122 } | |
123 | |
124 // from https://www.geeksforgeeks.org/how-to-check-if-a-given-point-lies-inside-a-polygon/ | |
125 // Given three colinear points p, q, r, the function checks if | |
126 // point q lies on line segment 'pr' | |
127 bool onSegment(const MaskPoint& p, const MaskPoint& q, const MaskPoint& r) | |
128 { | |
129 if (q.x <= std::max(p.x, r.x) && q.x >= std::min(p.x, r.x) && | |
130 q.y <= std::max(p.y, r.y) && q.y >= std::min(p.y, r.y)) | |
131 return true; | |
132 return false; | |
133 } | |
134 | |
135 // To find orientation of ordered triplet (p, q, r). | |
136 // The function returns following values | |
137 // 0 --> p, q and r are colinear | |
138 // 1 --> Clockwise | |
139 // 2 --> Counterclockwise | |
140 int orientation(const MaskPoint& p, const MaskPoint& q, const MaskPoint& r) | |
141 { | |
142 int val = (q.y - p.y) * (r.x - q.x) - | |
143 (q.x - p.x) * (r.y - q.y); | |
144 | |
145 if (val == 0) return 0; // colinear | |
146 return (val > 0)? 1: 2; // clock or counterclock wise | |
147 } | |
148 | |
149 // The function that returns true if line segment 'p1q1' | |
150 // and 'p2q2' intersect. | |
151 bool doIntersect(const MaskPoint& p1, const MaskPoint& q1, const MaskPoint& p2, const MaskPoint& q2) | |
152 { | |
153 // Find the four orientations needed for general and | |
154 // special cases | |
155 int o1 = orientation(p1, q1, p2); | |
156 int o2 = orientation(p1, q1, q2); | |
157 int o3 = orientation(p2, q2, p1); | |
158 int o4 = orientation(p2, q2, q1); | |
159 | |
160 // General case | |
161 if (o1 != o2 && o3 != o4) | |
162 return true; | |
163 | |
164 // Special Cases | |
165 // p1, q1 and p2 are colinear and p2 lies on segment p1q1 | |
166 if (o1 == 0 && onSegment(p1, p2, q1)) return true; | |
167 | |
168 // p1, q1 and p2 are colinear and q2 lies on segment p1q1 | |
169 if (o2 == 0 && onSegment(p1, q2, q1)) return true; | |
170 | |
171 // p2, q2 and p1 are colinear and p1 lies on segment p2q2 | |
172 if (o3 == 0 && onSegment(p2, p1, q2)) return true; | |
173 | |
174 // p2, q2 and q1 are colinear and q1 lies on segment p2q2 | |
175 if (o4 == 0 && onSegment(p2, q1, q2)) return true; | |
176 | |
177 return false; // Doesn't fall in any of the above cases | |
178 } | |
179 | |
180 // Define Infinite (Using INT_MAX caused overflow problems) | |
181 #define MASK_INF 1000000 | |
182 | |
183 // Returns true if the point p lies inside the polygon[] with n vertices | |
184 bool isInside(const std::vector<MaskPoint>& polygon, const MaskPoint& p) | |
185 { | |
186 // There must be at least 3 vertices in polygon[] | |
187 if (polygon.size() < 3) return false; | |
188 | |
189 // Create a point for line segment from p to infinite | |
190 MaskPoint extreme = {MASK_INF, p.y}; | |
191 | |
192 // Count intersections of the above line with sides of polygon | |
193 int count = 0, i = 0; | |
194 do | |
195 { | |
196 int next = (i+1) % polygon.size(); | |
197 | |
198 // Check if the line segment from 'p' to 'extreme' intersects | |
199 // with the line segment from 'polygon[i]' to 'polygon[next]' | |
200 if (doIntersect(polygon[i], polygon[next], p, extreme)) | |
201 { | |
202 // If the point 'p' is colinear with line segment 'i-next', | |
203 // then check if it lies on segment. If it lies, return true, | |
204 // otherwise false | |
205 if (orientation(polygon[i], p, polygon[next]) == 0) | |
206 return onSegment(polygon[i], p, polygon[next]); | |
207 | |
208 count++; | |
209 } | |
210 i = next; | |
211 } while (i != 0); | |
212 | |
213 // Return true if count is odd, false otherwise | |
214 return count&1; // Same as (count%2 == 1) | |
215 } | |
216 | |
217 | |
218 void RadiographyMaskLayer::DrawMask() const | |
219 { | |
220 unsigned int left; | |
221 unsigned int right; | |
222 unsigned int top; | |
223 unsigned int bottom; | |
224 | |
225 ComputeMaskExtent(left, right, top, bottom, corners_); | |
226 | |
227 Orthanc::ImageProcessing::Set(*mask_, 0); | |
228 | |
229 MaskPoint p(left, top); | |
230 for (p.y = top; p.y <= bottom; p.y++) | |
231 { | |
232 unsigned char* q = reinterpret_cast<unsigned char*>(mask_->GetRow(p.y)); | |
233 for (p.x = left; p.x <= right; p.x++, q++) | |
234 { | |
235 if (isInside(corners_, p)) | |
236 { | |
237 *q = 255; | |
238 } | |
239 } | |
240 } | |
241 | |
242 // Orthanc::ImageAccessor region; | |
243 // mask_->GetRegion(region, 100, 100, 1000, 1000); | |
244 // Orthanc::ImageProcessing::Set(region, 255); | |
245 } | |
246 | |
247 } |